realm / realm-java

Realm is a mobile database: a replacement for SQLite & ORMs
http://realm.io
Apache License 2.0
11.46k stars 1.75k forks source link

io.realm.exceptions.RealmFileException: Realm file is currently open in another process which cannot share access with this process. All processes sharing a single file must be the same architecture. Incompatible lock file. Shared info version doesn't match, 7 10. #4905

Closed vizZ closed 7 years ago

vizZ commented 7 years ago

Goal

I was simply trying to update Realm from 0.90.1 to 3.3.1.

Expected Results

Normal app behaviour.

Actual Results

We get reports (Crashlytics) of

io.realm.exceptions.RealmFileException: Realm file is currently open in another process which cannot share access with this process. All processes sharing a single file must be the same architecture. Incompatible lock file. Shared info version doesn't match, 7 10

happening only on Android 4.4.*.

Steps & Code to Reproduce

Not able to reproduce.

Code Sample

The way we use Realm:

Version of Realm and tooling

Realm version(s): 0.90.1 to 3.3.1

Realm sync feature enabled: no

Android Studio version: Android Studio 3.0 Canary 4

Which Android version and device: 4.4.* ONLY

vizZ commented 7 years ago

Have misclicked save, editing in progress ;d

Zhuinden commented 7 years ago

Do you initialize Realm in Application.onCreate()?

Do you open Realm with Realm.getDefaultInstance() or Realm.getInstance() in Application.onCreate()?

beeender commented 7 years ago

Hello! We have been hunting this issue for a long time, it could be the same issue with #2459 you can see lots of information there.

But, can you reproduce this issue? If you can, that is great! would you please let us know the steps to reproduce it including what 4.4 device is required as well as the apk files (both 0.90.1 & 3.3.1, you can send it to help@realm.io )

vizZ commented 7 years ago

Do you initialize Realm in Application.onCreate()?

I think the answer is not a simple one here - No & ~Yes.

No - Application does not inject/use Realm before Application.ActivityLifecycleCallbacks.onActivityCreated() was called

~Yes - WidgetService is no Activity, so there is a possibility that Realm instance is obtained with Realm.getDefaultInstance() before any Activity is CREATED. Event though that does not happen in Application.onCreate(), I think it's good to keep that one in mind and put a question mark here (answering ~Yes). And yes, I know that the Widget is supposed to "run in the same process" (I've checked the StackOverflow threads to make sure), but what if there are some custom changes to Launcher/OS (happens mostly on Samsung and LGE devices ~= 85%)?

I did quite a research on this topic (I'm aware of similar issues reported), but haven't been able to reproduce this issue, find a fix based on theoretical thinking and I'm running out of Mana here... :/

Do you open Realm with Realm.getDefaultInstance() or Realm.getInstance() in Application.onCreate()?

We only use Realm.getDefaultInstance() in a block of code that I copied (Dagger2 module).

Hello! We have been hunting this issue for a long time, it could be the same issue with #2459 you can see lots of information there.

As far as I know and understand, it's related to both #2459 and #4777.

But, can you reproduce this issue?

I was not able to reproduce it, but I don't think this is related to some "supposed to be shady update mechanism in Google Play Store" as the crash keeps being reported for affected Users (the ratio of Crash/Users is like 10/1, so it looks like the Users cannot even enter the app).

beeender commented 7 years ago

@vizZ

  1. I guess you are not having multi-processes services in your apk
  2. is it possible for you to contact one of those users who are suffering from this crash?
vizZ commented 7 years ago
vizZ commented 7 years ago

Here is the full Stacktrace:

Fatal Exception: java.lang.RuntimeException: Unable to start activity ComponentInfo{se.klart.weatherapp/se.klart.weatherapp.forecast.overview.MainActivity}: io.realm.exceptions.RealmFileException: Realm file is currently open in another process which cannot share access with this process. All processes sharing a single file must be the same architecture. (Incompatible lock file. Shared info version doesn't match, 7 10.) (/data/data/se.klart.weatherapp/files/default.realm) in /home/cc/repo/realm/release/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp line 233 Kind: INCOMPATIBLE_LOCK_FILE.
       at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2377)
       at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2429)
       at android.app.ActivityThread.access$800(ActivityThread.java:151)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1342)
       at android.os.Handler.dispatchMessage(Handler.java:110)
       at android.os.Looper.loop(Looper.java:193)
       at android.app.ActivityThread.main(ActivityThread.java:5333)
       at java.lang.reflect.Method.invokeNative(Method.java)
       at java.lang.reflect.Method.invoke(Method.java:515)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:824)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:640)
       at dalvik.system.NativeStart.main(NativeStart.java)
Caused by io.realm.exceptions.RealmFileException: Realm file is currently open in another process which cannot share access with this process. All processes sharing a single file must be the same architecture. (Incompatible lock file. Shared info version doesn't match, 7 10.) (/data/data/se.klart.weatherapp/files/default.realm) in /home/cc/repo/realm/release/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp line 233
       at io.realm.internal.SharedRealm.nativeGetSharedRealm(SharedRealm.java)
       at io.realm.internal.SharedRealm.(SharedRealm.java:192)
       at io.realm.internal.SharedRealm.getInstance(SharedRealm.java:244)
       at io.realm.internal.SharedRealm.getInstance(SharedRealm.java:208)
       at io.realm.RealmCache.doCreateRealmOrGetFromCache(RealmCache.java:298)
       at io.realm.RealmCache.createRealmOrGetFromCache(RealmCache.java:284)
       at io.realm.Realm.getDefaultInstance(Realm.java:273)
       at se.klart.weatherapp.AppModule.provideRealm(AppModule.java:59)
       at se.klart.weatherapp.AppModule_ProvideRealmFactory.get(AppModule_ProvideRealmFactory.java:30)
       at se.klart.weatherapp.AppModule_ProvideRealmFactory.get(AppModule_ProvideRealmFactory.java:10)
       at dagger.internal.DoubleCheck.get(DoubleCheck.java:47)
       at se.klart.weatherapp.data.DataModule_ProvideCacheV1Factory.get(DataModule_ProvideCacheV1Factory.java:29)
       at se.klart.weatherapp.data.DataModule_ProvideCacheV1Factory.get(DataModule_ProvideCacheV1Factory.java:10)
       at dagger.internal.DoubleCheck.get(DoubleCheck.java:47)
       at se.klart.weatherapp.data.DataModule_ProvideDataV1RepositoryFactory.get(DataModule_ProvideDataV1RepositoryFactory.java:61)
       at se.klart.weatherapp.data.DataModule_ProvideDataV1RepositoryFactory.get(DataModule_ProvideDataV1RepositoryFactory.java:12)
       at dagger.internal.DoubleCheck.get(DoubleCheck.java:47)
       at se.klart.weatherapp.DaggerAppComponent.getDataV1Repository(DaggerAppComponent.java:530)
       at se.klart.weatherapp.KlartApplication.trackInitialInformation(KlartApplication.java:89)
       at se.klart.weatherapp.KlartApplication.onActivityCreated(KlartApplication.java:101)
       at android.app.Application.dispatchActivityCreated(Application.java:189)
       at android.app.Activity.onCreate(Activity.java:907)
       at android.support.v4.app.BaseFragmentActivityGingerbread.onCreate(BaseFragmentActivityGingerbread.java:54)
       at android.support.v4.app.FragmentActivity.onCreate(FragmentActivity.java:319)
       at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:85)
       at se.klart.weatherapp.forecast.overview.MainActivity.onCreate(MainActivity.java:222)
       at android.app.Activity.performCreate(Activity.java:5343)
       at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1088)
       at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2331)
       at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2429)
       at android.app.ActivityThread.access$800(ActivityThread.java:151)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1342)
       at android.os.Handler.dispatchMessage(Handler.java:110)
       at android.os.Looper.loop(Looper.java:193)
       at android.app.ActivityThread.main(ActivityThread.java:5333)
       at java.lang.reflect.Method.invokeNative(Method.java)
       at java.lang.reflect.Method.invoke(Method.java:515)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:824)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:640)
       at dalvik.system.NativeStart.main(NativeStart.java)
vizZ commented 7 years ago

@beeender I've sent the mail with additional info (files) to help@realm.io (the title is the issue id 4905)

vizZ commented 7 years ago

I think it's best to assume that there is some number of Users that cannot enter the app. Like "at all" or "frequently".

Now if the problem is the "update", then I think I'm ok with deleting Realm and creating the db again. But the problem is, from what I understand when reading the docs: https://realm.io/docs/java/latest/api/io/realm/Realm.html#deleteRealm-io.realm.RealmConfiguration- All Realm instances must be closed before calling this method.

but Realm is opened (what fails is the Realm.getDefaultInstance()), this is why we have a problem in the very first place, right?

beeender commented 7 years ago

@vizZ Realm.deleteRealm() is not multi-processes safe. That is the very annoy part of this issue. From all the evidence we got so far indicates that there is another old version apk process is still running for some reason, and that one is holding an opened Realm.

The deleteRealm() will only check if there is any threads have an opened Realm and delete the file if there is no. So, in this case, the deleteRealm() will actually delete the files since it doesn't check other processes . It might work, but it might also have a small chance damage the db file.

If you want to try to detect if the problem happens and then reset the file, renaming the Realm to something else and set it as the default will be a safer solution.

To be honest, according to the information we collected, i think the chance that Users that cannot enter the app at all is quite small. Usually from the trend of the crash rate, it happens a lot at the beginning we bump the lock file version, then it get decreased day by day. Looks very much like during the updating, the old apk process is not termintated :(

vizZ commented 7 years ago

@beeender By renaming the Realm, do You mean Realm.getDefaultInstance().copyToRealm(otherRealm) or is there any other way of "just changing the Realm file name?" (docs are not were verbose about such scenario).

Zhuinden commented 7 years ago

You're opening instances you cannot close with Realm.getDefaultInstance().close() and Realm.getDefaultInstance().copyToRealm(otherRealm) and literally any call to Realm.getDefaultInstance() where you ignore the return value and do not close it.

beeender commented 7 years ago

By renaming the Realm, do You mean Realm.getDefaultInstance().copyToRealm(otherRealm) or is there any other way of "just changing the Realm file name?" (docs are not were verbose about such scenario).

hmm, i actually mean create a new Realm with different name. But this inspires me that copying the old file may actually solve the problem in this case. copyToRealm() won't work here since it requires to open the old realm file first. you can get the path from the config by RealmConfiguration.getPath() then copy it as a new different name file then open it with a different name Realm. BUT, PLEASE PLEASE notice This could be very dangerous if the destination exists and has been opened as a Realm instance. It could damage the db file forever in this case.

So, if you don't get any complain from users about this crash, I really suggest you just wait and see, according to the reports, no actual real end users report this issue. So it might just be a silent reset and user could start the apk normally. adding hacks to work around this may cause some other issues we haven't expected, since we don't really know what is the root cause of the original issue.

How is the trend of your crash rate? it is getting better day by day?

vizZ commented 7 years ago

Actually, what I was thinking about is something as nasty as this:

But I can spot some scary monsters ahead:

vizZ commented 7 years ago

Funny thing is, we are now doing a 3rd staged roll-out since spotting the issue and applying the Application.ActivityLifecycleCallbacks-based Realm lifecycle-related management and there are no reports of the crash with the latest version @ 5% adoption rate... Oo

I will keep You updated.

beeender commented 7 years ago

actually your idea doesn't seem to be bad if the concerns can be addressed:

since we have no idea when is this crash happening, there is a chance that the catch part of the try-catch block would be used more than once for single installation/update of 4.4.*, which would mean that the app would try to replace an already replaced db (which would mean that its due to a "normal", frequent behavior, not some Google Play Store update)

Just delete the old realm file after copying? if you can address the thread safety issues, eg.: always do it in Application.onCreate() ? (be noticed, there are some devices in the markets have bugs that Application.onCreate() is not called :( )

so we would need to check if the newRealm exists, which could crash in the same way as Realm.getDefaultInstance()

copy and delete old realm, then check the existence of the old realm might be safer i think. Or something like

if (newRealmExist() || oldRealmNotExist) {
    // Just use the new name
}

and we would need to prevent this "migration" for new installations and "app data resets" (I can see no way of doing such a thing via regular Realm migration, right?)

above logic could solve this i think? in this case, just use the new realm name.

beeender commented 7 years ago

BTW, you can verify your idea by manually making a broken realm file, eg, just generate a random file and name it as the same with realm file.

beeender commented 7 years ago

INTERNAL NOTES: more information can be found https://secure.helpscout.net/conversation/391493053/10525/?folderId=539748

vizZ commented 7 years ago

Btw, I think there is a bug in Migration API. Consider this example:

            schema.get("Something")
                    .transform(obj -> {
                        if (obj.get("column_name") != null) {
                            // DO SOMETHING ABOUT IT
                        }
                    });

When migrating from 0.90.1 (schema version 3) to 3.3.1 (schema version 4) this code is supposed to be triggered on != null values (of Long type). The problem is, the obj.get() translates the null values to 0... Is that a known thing?

vizZ commented 7 years ago

Also, we are doing much better with the issue now, I can see just a few crashes reported with the latest update (like 10 times less).

No real fix to the issue itself was applied in the meantime... Oo

beeender commented 7 years ago

@vizZ Before that obj.get() called, the column is optional or required?

vizZ commented 7 years ago

@beeender it is not marked as @Required.

beeender commented 7 years ago

@vizZ

  1. What is the declaration of column_name field before and after
  2. possible to share your whole migration block?
vizZ commented 7 years ago

@beeender FYI, I've released an update which removed the dependency on Realm from the Widget (I cannot think of any other relevant change with this release; the widget would make the Application open Realm from "outside" of registered ActivityLifecycleCallbacks). The effect is a drop from ~200 affected users to ~15. This may be just a coincidence, but there were several updates in the meantime and each of them reported the same amount of affected users - ~200.

vizZ commented 7 years ago

@beeender about the column_name, the column is not used/removed after the migration is over and the declaration was a dead-simple Long variable.

vizZ commented 7 years ago

@beeender the migration looks like this (this is a version with a "fix" for reporting nil/null as 0 (fortunately we would never set 0 for this property in our own code):

schema.get("Thing")
        .transform(obj -> {
            if (obj.<Long>get("column_name") != null && obj.<Long>get("column_name") > 0) {
                DynamicRealmObject tagObject = realm.createObject("Tag", obj.get("id"));
                tagObject.setLong("taggedAt", obj.getLong("taggedAt"));
            }
        });

schema.remove("Thing");
RealmObjectSchema placeObjectSchema = schema.create("Thing")
        .addField("id", String.class, FieldAttribute.PRIMARY_KEY)
// some more addField() code here
beeender commented 7 years ago

weird, the widget code should be running in the same app process unless you specify a different process in the manifest ....

vizZ commented 7 years ago

@beeender It may be just a false lead. Anyway, out of ideas here. Just wanted You to know about the "widget fix".

bmeike commented 7 years ago

Hey @vizZ ! How are we doing on this issue? Is there still a problem? Have you, by any chance, figured out why there are two processes? You haven't changed the package name, in your manifest? -blake

bmeike commented 7 years ago

I suspect that this has nothing to do with multiple processes. The cause of failure is here:

if (info->shared_info_version != g_shared_info_version) {
    std::stringstream ss;
    ss << "Shared info version doesn't match, " << info->shared_info_version << " " << g_shared_info_version
       << ".";
    throw IncompatibleLockFile(ss.str());
}

Looks to me like the lock file format is corrupt or old.

beeender commented 7 years ago

@vizZ I made some updates to the original issue, see https://github.com/realm/realm-java/issues/2459#issuecomment-321786764

I am closing this issue now. Let's discuss in #2459 if it is still needed (:P I hope it is not needed anymore).