Closed vizZ closed 7 years ago
Have misclicked save
, editing in progress ;d
Do you initialize Realm in Application.onCreate()
?
Do you open Realm with Realm.getDefaultInstance()
or Realm.getInstance()
in Application.onCreate()
?
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 )
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... :/
We only use Realm.getDefaultInstance()
in a block of code that I copied (Dagger2
module).
As far as I know and understand, it's related to both #2459 and #4777.
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).
@vizZ
Service
s, except for the WidgetService
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)
@beeender I've sent the mail with additional info (files) to help@realm.io (the title is the issue id 4905
)
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?
@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 :(
@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).
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.
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?
Actually, what I was thinking about is something as nasty as this:
Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP
wrap the AppModule
's Realm.getDefaultInstance()
in a try-catch
block (sic!)
defaultRealm.copyToRealm(newRealm)
and start using the newRealm
newRealm
without copying anything (the data will be lost, but the Users will be able to use/enter the app)defaultRealm.copyToRealm(newRealm)
and start using the newRealm
for any other Build.VERSION.SDK_INT
(minSdkVersion = 19
)But I can spot some scary monsters ahead:
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)newRealm
exists, which could crash in the same way as Realm.getDefaultInstance()
Realm
migration, right?)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.
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.
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.
INTERNAL NOTES: more information can be found https://secure.helpscout.net/conversation/391493053/10525/?folderId=539748
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?
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
@vizZ Before that obj.get()
called, the column is optional or required?
@beeender it is not marked as @Required
.
@vizZ
column_name
field before and after@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
.
@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
.
@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
weird, the widget code should be running in the same app process unless you specify a different process in the manifest ....
@beeender It may be just a false lead. Anyway, out of ideas here. Just wanted You to know about the "widget fix".
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
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.
@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).
Goal
I was simply trying to update
Realm
from0.90.1
to3.3.1
.Expected Results
Normal app behaviour.
Actual Results
We get reports (Crashlytics) of
happening only on
Android 4.4.*
.Steps & Code to Reproduce
Not able to reproduce.
Code Sample
The way we use
Realm
:Realm
withRealm.init(this);
inApplication.onCreate()
callbackRealm.getDefaultInstance()
viaDagger2
(AppComponent/AppModule
) inMainActivity
Realm.getDefaultInstance().close()
inApplication.ActivityLifecycleCallbacks.onActivityDestroyed()
, but only when theActivity
s count is0
Widget
s are my current lead here), but we do provide widgets and inject dependencies toWidgetService
(these dependencies do depend onRealm
, soRealm.getDefaultInstance()
is called when widgets gets refreshedVersion of Realm and tooling
Realm version(s):
0.90.1
to3.3.1
Realm sync feature enabled:
no
Android Studio version:
Android Studio 3.0 Canary 4
Which Android version and device:
4.4.* ONLY