objectbox-java icon indicating copy to clipboard operation
objectbox-java copied to clipboard

io.objectbox.exception.FileCorruptException: Could not get from cursor (corrupted) (error code -30796)

Open tomwassink opened this issue 5 years ago • 7 comments

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)

tomwassink avatar Nov 12 '20 21:11 tomwassink

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...).

greenrobot avatar Nov 13 '20 16:11 greenrobot

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.

tomwassink avatar Nov 16 '20 13:11 tomwassink

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

greenrobot avatar Nov 16 '20 13:11 greenrobot

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)

tomwassink avatar Nov 18 '20 10:11 tomwassink

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.

tomwassink avatar Nov 22 '20 09:11 tomwassink

As

  • code -30796 is MDB_CORRUPTED and
  • 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?

greenrobot-team avatar Nov 23 '20 09:11 greenrobot-team

What I did:

  • When the app starts the MainActivity class starts. In its onResume method a foreground service (remote process) is started (via the JepsterManager class 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 MainActivity class

  • Then it waits till it is connected to the service (then the initMainActivity method as posted is called)

  • In initMainActivity ObjectBox.initReadOnly is called (via the JepsterManager class)

  • When the service starts ObjectBox.init is 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.

tomwassink avatar Nov 25 '20 19:11 tomwassink