io.objectbox.exception.FileCorruptException: Could not get from cursor (corrupted) (error code -30796)
In my Pre-launch report details I see continuously crashes on Android 8.1 phones. I cannot reproduce it on a Emulator with SDK 27. Stacktrace error (see for log details below): io.objectbox.exception.FileCorruptException: Could not get from cursor (corrupted) (error code -30796)
Is this a issue with pre-launch testing or might this occur on real devices as well? After searching I found multiple issues with Android 8.1. Any clue?
I'm using a read-only box from a fragment (write box is in a remote service).
- ObjectBox version 2.8.0 (but also on 2.7.1)
- Reproducibility: always on Android 8.1 devices for pre-launch report
- Device: e.g. Motorola moto e5 play and Nokia Nokia 1
- OS: Android 8.1
Code Initialization done in MainActivity class after being connected to Service (remote process). The method initMainActivity is called.
protected void initMainActivity() {
if (!mInitMainActivity) {
JepsterManager jepsterManager = (JepsterManager) getApplication();
jepsterManager.initReadOnlyBox();
//Check if version is updated and update information has to be shown
JepsterVersionUtilities.checkVersion(this, mService, mPreferences);
//Check version to verify if preference migration is needed
PrefsMigrationUtilities.checkVersion(this, mPreferences);
setNavigationHeader();
mInitMainActivity = true;
//ObjectBox need to be available, JepsterService has to be started first to be able to update schema of Box.
if (mOnPostResume){
selectDrawerItem(mMenuItemId);
}
}
}
public class ObjectBox {
private static BoxStore boxStore;
private static int messageId = 0;
public static void init(Context context) {
if (boxStore == null) {
boxStore = MyObjectBox.builder()
.androidContext(context.getApplicationContext())
.androidReLinker(ReLinker.log(new ReLinker.Logger() {
@Override
public void log(String message) {
Log.d(Constants.TAG, message);
}
}))
.build();
if (BuildConfig.DEBUG) {
boolean started = new AndroidObjectBrowser(boxStore).start(context);
}
}
}
public static void initReadOnly(Context context) {
if (boxStore == null) {
boxStore = MyObjectBox.builder()
.androidContext(context.getApplicationContext())
.androidReLinker(ReLinker.log(new ReLinker.Logger() {
@Override
public void log(String message) {
Log.d(Constants.TAG, message);
}
}))
.readOnly()
.build();
if (BuildConfig.DEBUG) {
boolean started = new AndroidObjectBrowser(boxStore).start(context);
}
}
}
public static BoxStore get() {
return boxStore;
}
public static int getMessageId(){
messageId ++;
return messageId;
}
}
public int getDataFieldValue(int bike, int page, int dataField){
Box<BikeDataFieldEntity> bikeDataFieldBox = getBikeDataFieldBox();
//Search data field ID
QueryBuilder<BikeDataFieldEntity> builder = bikeDataFieldBox.query();
builder.equal(BikeDataFieldEntity_.bike, bike)
.equal(BikeDataFieldEntity_.page, page)
.equal(BikeDataFieldEntity_.dataField, dataField);
Query<BikeDataFieldEntity> query = builder.build();
BikeDataFieldEntity bikeDataField = query.findFirst(); (**Line 321**)
return bikeDataField.getDataFieldValue();
}
public Box<BikeDataFieldEntity> getBikeDataFieldBox(){
if (mBikeDataFieldBox == null){
mBikeDataFieldBox = ObjectBox.get().boxFor(BikeDataFieldEntity.class);
}
return mBikeDataFieldBox;
}
Logs, stack traces
io.objectbox.exception.FileCorruptException: Could not get from cursor (corrupted) (error code -30796)
at io.objectbox.query.Query.nativeFindFirst(Native Method)
at io.objectbox.query.Query.lambda$findFirst$0$Query(Query.java:153)
at io.objectbox.query.-$$Lambda$Query$U3Pw2NlOHcqDE3imeFDwfgwkeJw.call(Unknown Source:2)
at io.objectbox.BoxStore.callInReadTx(BoxStore.java:813)
at io.objectbox.BoxStore.callInReadTxWithRetry(BoxStore.java:759)
at io.objectbox.query.Query.callInReadTx(Query.java:275)
at io.objectbox.query.Query.findFirst(Query.java:151)
at com.twom.bico.pages.FragmentPageDataFields.getDataFieldValue(FragmentPageDataFields.java:321)
at com.twom.bico.pages.FragmentPageDataFields.initViews(FragmentPageDataFields.java:111)
at com.twom.bico.pages.FragmentPage9DataFields.onCreateView(FragmentPage9DataFields.java:31)
at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2698)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:320)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1187)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1356)
at androidx.fragment.app.FragmentManager.moveFragmentToExpectedState(FragmentManager.java:1434)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1497)
at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:447)
at androidx.fragment.app.FragmentManager.executeOps(FragmentManager.java:2169)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1992)
at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1947)
at androidx.fragment.app.FragmentManager.execSingleAction(FragmentManager.java:1818)
at androidx.fragment.app.BackStackRecord.commitNowAllowingStateLoss(BackStackRecord.java:303)
at androidx.fragment.app.FragmentStatePagerAdapter.finishUpdate(FragmentStatePagerAdapter.java:270)
Android 8.1 - it's not the first time that this version makes trouble. Also the devices you mentioned seem familiar - I think those corrupted files from time to time... All we can do is to try to work around a likely broken file system.
Please try the following:
https://github.com/objectbox/objectbox-examples/blob/5898d12d658866235a165468373bd63708f6afe7/android-app/src/main/java/io/objectbox/example/ObjectBox.java#L17-L28
Also, are you ensuring that the writing process is setting up the database completely, before the read-only process accesses it? If that doesn't happen, the read-only process can not initialize the database (it's read-only after all...).
Thanks for your fast reply. I tried your workaround, but the issues stayed the same. I published the app also in the Play store (small roll out) but got (relatively) quite a lot of crash reports.
I just published another update with a normal box for the read only activity (so called just ObjectBox.init() instead of ObjectBox.initReadOnly(). Write actions are only done by the remote process. The remote process will also first build a BoxStore as well.
This seems to work (no issues in the pre-launch report anymore). Reported crashes is just only one up to now. But it seems to be another issue (or not?).
java.util.concurrent.TimeoutException:
at io.objectbox.BoxStore.close (BoxStore.java:554)
at io.objectbox.BoxStore.finalize (BoxStore.java:403)
at java.lang.Daemons$FinalizerDaemon.doFinalize (Daemons.java:289)
at java.lang.Daemons$FinalizerDaemon.runInternal (Daemons.java:276)
at java.lang.Daemons$Daemon.run (Daemons.java:137)
at java.lang.Thread.run (Thread.java:919)
I will post an update within a couple of days to see if there are still issues.
Are you saying the read-only store might potentially cause a higher frequency of FileCorruptException? And thanks for keeping us updated.
TimeoutException: yeah likely a separate issue
Update: up to 2020-11-18 11:40h no new crashes reported with FileCorruptionException. So it seems that it is somehow related to readOnly vs. "write" box.
Only one DbException:
io.objectbox.exception.DbException:
at io.objectbox.BoxStore.verifyNotAlreadyOpen (BoxStore.java:332)
at io.objectbox.BoxStore.<init> (BoxStore.java:261)
at io.objectbox.BoxStoreBuilder.build (BoxStoreBuilder.java:498)
at com.twom.bico.helper.ObjectBox.init (ObjectBox.java:34)
Update 2020-11-22 10:40h: still no new FileCorruptoinExceptions. I only had 4 users with a crash with a DbException within the verifyNotAlreadyOpen method and 2 users with a crash after a TimeOutExceptoin within the close method.
As
- code -30796 is
MDB_CORRUPTEDand - this is not like the other Android 8.1 issues and
- using two write-enabled stores works,
it sounds like a concurrency issue.
As @greenrobot mentioned, do you ensure that ObjectBox.init() completes before ObjectBox.initReadOnly() is called? (Your code examples only show the read-only part, no?)
But yeah, if in pre-launch test it only affects Android 8.1 (they also run on Android 8.0 and 9.0 AFAIK) it might be an Android 8.1 file system specific bug we are not aware of, yet.
Edit: given the verifyNotAlreadyOpen exception I'm wondering if your code closes and re-builds the BoxStore at some point?
What I did:
-
When the app starts the
MainActivityclass starts. In itsonResumemethod a foreground service (remote process) is started (via theJepsterManagerclass which extends the application class. This is only done when the service isn't started yet). -
The next step is to bind to the service from the
MainActivityclass -
Then it waits till it is connected to the service (then the
initMainActivitymethod as posted is called) -
In
initMainActivityObjectBox.initReadOnlyis called (via theJepsterManagerclass) -
When the service starts
ObjectBox.initis called from the application (JepsterManager)
I also tried to execute the same query as in the getDataFieldValue method just before returning the messenger from the service:
@Override
public IBinder onBind(Intent intent){
//Make sure database is ready before read only box is initiated
//ObjectBoxUtilities.isDatabaseReady();
return mMessenger.getBinder();
}
But I had still the issues when creating the readOnly BoxStore.
// verifyNotAlreadyOpen exception log:
io.objectbox.exception.DbException:
at io.objectbox.BoxStore.verifyNotAlreadyOpen (BoxStore.java:332)
at io.objectbox.BoxStore.<init> (BoxStore.java:261)
at io.objectbox.BoxStoreBuilder.build (BoxStoreBuilder.java:498)
at com.twom.bico.helper.ObjectBox.init (ObjectBox.java:34)
at com.twom.bico.JepsterManager.initReadOnlyBox (JepsterManager.java:102)
at com.twom.bico.MainActivity.initMainActivity (MainActivity.java:373)
at com.twom.bico.AbstractInterProcessCommunicationActivity$1.onServiceConnected (AbstractInterProcessCommunicationActivity.java:122)
at android.app.LoadedApk$ServiceDispatcher.doConnected (LoadedApk.java:1962)
at android.app.LoadedApk$ServiceDispatcher$RunConnection.run (LoadedApk.java:1994)
at android.os.Handler.handleCallback (Handler.java:883)
at android.os.Handler.dispatchMessage (Handler.java:100)
at android.os.Looper.loop (Looper.java:241)
at android.app.ActivityThread.main (ActivityThread.java:7582)
at java.lang.reflect.Method.invoke (Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:941)
I do not close the box in my app. I only create a BoxStore in the ObjectBox class as posted. There is always a check if boxStore is null.