Android Realm Bad Transaction Log - android

I am using Realm to persist data in my Android app and just got this crash with Realm. The circumstances: I am using RealmResults in a Fragment. There is a trigger to switch to another set of data, but it has not yet been saved to Realm. So I read the new from a file, save it to Realm then re-run my query to overwrite the prior RealmResults in the same Fragment.
io.realm.exceptions.RealmError: Unrecoverable error. Bad transaction log in /Users/cm/Realm/realm-java/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp line 210
at io.realm.internal.SharedRealm.nativeRefresh(Native Method)
at io.realm.internal.SharedRealm.refresh(SharedRealm.java:281)
at io.realm.HandlerController.realmChanged(HandlerController.java:450)
at io.realm.HandlerController.handleMessage(HandlerController.java:121)
at android.os.Handler.dispatchMessage(Handler.java:98)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:6117)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194)
I found this response (https://github.com/realm/realm-java/issues/3702) in response to the same crash but I do not understand the concept of the response:
Bad transaction log could indicate problems with the file somehow.
I see you are using Sync. Note that you cannot open an old Realm in Sync mode. You need to move all data there. Trying to open an old Realm as syncable will result in the error you are seeing.
Can someone point me in the right direction? What does "open an old Realm in Sync mode" mean? Do I need to close Realm in my Fragment and get a new Realm instance before overwriting my RealmResults? This is highly intermittent, so it's not a matter of simply trying it once and seeing if it fixes the problem.
UPDATE: Finally fixed by not opening any Realm instance in MainActivity. So I have to check for schema migration in one of its ViewPager fragments. (FWIW the crash occurs when beginTransaction() is called in an AsyncTask of a Fragment, though I can see from my error logging that the AsyncTask continues to work after the crash). I think the problem is related to using Realm in an AsyncTask of a Fragment. The reason I think this is because I have another Activity with a ViewPager, but no problems with Realm, I assume because those ViewPager fragments do not utilize Realm + AsyncTask.
Although I no longer believe my crash is related to attempted schema migration, as requested, here is how I do that:
RealmConfiguration config = new RealmConfiguration.Builder()
.name("my_realm.realm")
.schemaVersion(MyConstants.MY_REALM_SCHEMA_VERSION)
.modules(new MyRealmModule())
.build();
try {
Realm.migrateRealm(config, new MigrationMyRealm());
System.out.println("debug tag, migrated realm");
} catch (FileNotFoundException e) {
System.out.println("debug tag, there is no realm to migrate");
} catch (IllegalStateException e) {
// realm is already open -- cannot migrate it
System.out.println("debug tag, could not migrate realm because it is open");
}

Related

Room Persistance Error

I have an app that uses Room persistence at production mode. Its seems when i try to update a table i have the following error on some devices: My connection with database is open all time, only on destroy i release the db.
This is my Service that runs every 12 hours
This is Service: https://gist.github.com/anonymous/8fac7650b34aa19229d5f6b91d2454d4
DataRepo :
https://gist.github.com/anonymous/70c524c1e8eb5e7ed893131a9c685b5b
AppDatabase
https://gist.github.com/anonymous/ccb20853054fa5d453592fd2653a4dc4
the error:
java.lang.IllegalStateException:
at android.database.sqlite.SQLiteDatabase.throwIfNotOpenLocked
(SQLiteDatabase.java:2199)
at android.database.sqlite.SQLiteDatabase.createSession
(SQLiteDatabase.java:379)
at android.database.sqlite.SQLiteDatabase$1.initialValue
(SQLiteDatabase.java:92)
at android.database.sqlite.SQLiteDatabase$1.initialValue
(SQLiteDatabase.java:89)
at java.lang.ThreadLocal$Values.getAfterMiss (ThreadLocal.java:430)
at java.lang.ThreadLocal.get (ThreadLocal.java:65)
at android.database.sqlite.SQLiteDatabase.getThreadSession
(SQLiteDatabase.java:373)
at android.database.sqlite.SQLiteProgram.getSession (SQLiteProgram.java:101)
at android.database.sqlite.SQLiteStatement.executeUpdateDelete
(SQLiteStatement.java:64)
at android.arch.persistence.db.framework.FrameworkSQLiteStatement.executeUpdateDelete (FrameworkSQLiteStatement.java:75)
at android.arch.persistence.room.EntityDeletionOrUpdateAdapter.handle (EntityDeletionOrUpdateAdapter.java:69)
at mbc.analytics.sdk.room.dao.TimeDao_Impl.updateTimeModel (TimeDao_Impl.java:122)
at mbc.analytics.sdk.room.database.DatabaseRepository.createTimeEntity (DatabaseRepository.java:211)
at mbc.analytics.sdk.room.database.DatabaseRepository.createAppEntity (DatabaseRepository.java:56)
at mbc.analytics.sdk.services.LollipopService.getStats (LollipopService.java:202)
at mbc.analytics.sdk.services.LollipopService.access$900 (LollipopService.java:39)
at mbc.analytics.sdk.services.LollipopService$2.run (LollipopService.java:153)
at java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:587)
at java.lang.Thread.run (Thread.java:818)
DatabaseRepository is wrapping a call to Room.databaseBuilder(). As this is implemented using a singleton, you're closing the database in the call to databaseRepository.databaseClose();, but not opening again. The creation of a new DatabaseRepository in your code doesn't help, as AppDatabase.getAppDatabase(ctx); will return the same closed database.
So, possible solutions will be:
Remove the call to databaseRepository.databaseClose();, as the service is running inside the same Application as the rest of your Activities, and the database is (and should be) shared. This is the preferred solution in my opinion.
An alternative will be that DatabaseRepository#databaseClose() also destroys the database object by calling AppDatabase.destroyInstance();. To me, this is can pose other issues, like concurrency issues, retained references to the old database object (e.g. in an activity), etc.
Code for the second, not recommended solution:
public void databaseClose() {
if (db.isOpen()) {
db.close();
AppDatabase.destroyInstance();
}
}

RealmError: Realm Out of memory size

I am using Realm 3.0.0 as the DB of my Android app. It's like a questionnaire application, in which the user navigates inside the app a lot. When I use the app (go back and forth) continuously, I get the following error:
Fatal Exception: io.realm.exceptions.RealmError: Unrecoverable error. mmap() failed: Out of memory size: 1073741824 offset: 0 in /Users/cm/Realm/realm-java/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp line 109
at io.realm.internal.SharedRealm.nativeGetSharedRealm(SharedRealm.java)
at io.realm.internal.SharedRealm.(SharedRealm.java:187)
at io.realm.internal.SharedRealm.getInstance(SharedRealm.java:229)
at io.realm.internal.SharedRealm.getInstance(SharedRealm.java:204)
at io.realm.RealmCache.createRealmOrGetFromCache(RealmCache.java:124)
at io.realm.Realm.getDefaultInstance(Realm.java:210)
Now I know the main cause of this is not closing Realm instances. But I've already checked for that multiple times. And I am positive that I close every instance I open.
The app has many activities and fragments that all get a Realm instance on their onCreate and close it on their onDestroy. There are also other background network jobs that run to upload data that get Realm instances. These jobs close their Realm instances when they've finished running or when they cancel.
All of the above get their Realm instance thru injection via Dagger 2:
#Provides
#Nullable
static Realm realm(#Nullable RealmConfiguration configuration) {
if (configuration != null) {
Realm.setDefaultConfiguration(configuration);
return Realm.getDefaultInstance();
}
return null;
}
Configuration is also provided in the same Dagger Module.
To be more specific, a Questionnaire consists of many Question Fragments displayed in a ViewPager. Each Fragment gets injected with a realm. Many interactions in a given Question Fragment write data to the DB (some async, some blocking). These Fragments also query the database on onResume to get their updated Data. Some of this data is also copied out of Realm via realm.copyFromRealm(). Now at any given time of these happening, an upload job is most likely running and reading data from the DB and uploading it to a server. When an upload job finishes, it then writes to the DB.
I think I can have up to 7-12 fragment/activities holding a realm reference on the UI thread at a given moment. And 0-6 other references on 0-3 other threads (Background Jobs).
Moreover, I compact my realm DB via Realm.compactRealm(realmConfiguration) on every app launch (perhaps as a separate problem, this doesn't seem to do it's job consistently).
Above I've tried to describe my Realm usage descriptively without going into details. Now my problem is, when a user excessively uses the app (going back and forth between activities/fragments (realm injection + DB read query), uploading data (realm injection + DB read&write query)), I get the above posted Out of Memory Error.
I am also using Leak Canary, and it hasn't detected any leaks. (Not sure if it can anyway)
Am I using Realm in a way it's not supposed to be used? Should I close Realm instances onPause instead of onDestroy? Should I have only one realm instance in an activity and have all it's fragmetns (up to 5 in my case) use this instance? What kind of changes can I make in my app, and perhaps my app architecture to solve this problem?
I appreciate any help in trying to solve this problem.
EDIT: I'm sharing the realm open-close logic in my background threads.
All my jobs share the same realm usage, which is the following:
Realm is injected lazily via:
#Inject protected transient Lazy<Realm> lazyRealm;
The realm object reference is held at the private transient Realm realm; field. I am using Android Priority Job Queue. When the job is added:
#Override
public void onAdded() {
realm = lazyRealm.get();
realm.executeTransaction(realm1 -> {
//write some stuff into realm
});
realm.close();
}
And when the job is run realm is retreived once, and every possible ending of this method has a call to realm.close()
#Override public void onRun() throws Throwable {
synchronized (syncAdapterLock) {
realm = lazyRealm.get();
Answer answer = realm.where(Answer.class).equalTo(AnswerQuery.ID, answerId).findFirst();
if (answer == null) {
realm.close();
throw new RealmException("File not found");
}
final File photoFile = new File(answer.getFilePath());
final Response response = answerService.uploadPhotoAnswer(answerId, RequestBody.create(MediaType.parse("multipart/form-data"), photoFile)).execute();
if (!response.isSuccessful()) {
realm.close();
throw new HttpError(statusCode);
}
realm.executeTransaction(realm1 -> {
answer.setSyncStatus(SyncStatus.SYNCED.getCode());
});
}
realm.close();
}
}
As you can see, these background threads do close their realm instances properly as far as I'm concerned.
While it was true that all my background tasks did call realm.close(), one of them called it too late in it's lifecycle. That was my GPSService, which is a background service. The problem was that GPS service is initialized at the launch of the App as an Android Service, which is rarely destroyed. I was injecting a realm instance onCreate and closing it onDestroy. After the comments of #EpicPandaForce and reading his articles about using realm properly. I realized that this was the cause of the leak. A non-looper thread was keeping an open realm reference for an extremely long time, thus, the mmap was bloating every time a write transaction occures. Now that I moved the realm get/close to happen every time the service runs, my problem is fixed.
My take away is that one needs to treat background thread realm access very delicately. Thank you both for your quick responses and help!

Resolving nested transactions when using realm and pagination

I'm using realm for persistence in my app.
At the moment, I have a list of items in a simple pagination fashion.
Make a server request
Get items back
Update the UI
Save list to realm
User scrolls to the end of the list, get the next set of elements and repeat
above
Realm code:
try {
mDbManager.beginTransaction();
mDbManager.copyToRealm(list);
mDbManager.commitTransaction();
Logger.v("Realm ", "Copied list to realm");
} catch (Exception e) {
Logger.e("Realm Something went wrong ", e);
}
I get the error because of this pagination loop:
java.lang.IllegalStateException: Nested transactions are not allowed. Use commitTransaction() after each beginTransaction().
I also tried closing the database after each commit but that didn't help.
How can I achieve this storage and resolve the nested transactions?
Or can anyone suggest a better design structure for this?
Thanks
Just make sure that while you catch the exception , you closes the transaction by mDbManager.closetransaction
You can use Realm.isInTransaction() to check if you have an open transaction.
Try to aviod this :
beginTransaction
beginTransaction
commitTransaction
commitTransaction
you should do something like this :
beginTransaction
commitTransaction
beginTransaction
commitTransaction
I think there're beginTransaction() before your code try to close it.

RealmMigrationNeededException when changing Realm model [duplicate]

This question already has answers here:
"realm migration needed", exception in android while retrieving values from realm db
(5 answers)
Closed 5 years ago.
Whenever I change the model like adding more fields, the app crash with io.realm.exceptions.RealmMigrationNeededException error. This can only be resolved when I uninstalled and reinstalled the app.
Any suggestion to do migration? I am using only the default instance.
If you don't have any problem in loosing your old data then you can delete Realm Configuration and create new one.
Realm realm = null;
try {
realm = Realm.getInstance(MainActivity.this);
} catch (RealmMigrationNeededException r) {
Realm.deleteRealmFile(MainActivity.this);
realm = Realm.getInstance(MainActivity.this);
}
OR
RealmConfiguration config2 = new RealmConfiguration.Builder(this)
.name("default2")
.schemaVersion(3)
.deleteRealmIfMigrationNeeded()
.build();
realm = Realm.getInstance(config2);
you have to do Migration if you don't want to loose your data please see this example here.
You should be able to find the information you need here:
https://realm.io/docs/java/latest/#migrations
Just changing your code to the new definition will work fine, if you
have no data stored on disk under the old database schema. But if you
do, there will be a mismatch between what Realm sees defined in code &
the data Realm sees on disk, so an exception will be thrown.
Realm migrations in 0.84.2 are changed quite a bit, the key points on making a realm (0.84.2) migration work for me were understanding that:
The schemaVersion is always 0 when your app has a realm db without
specifying the schemaVersion. Which is true in most cases since you
probably start using the schemaVersion in the configuration once you
need migrations & are already running a live release of your app.
The schemaVersion is automatically stored and when a fresh install of your app occurs and you are already on schemaVersion 3, realm
automatically checks if there are exceptions, if not it sets the
schemaVersion to 3 so your migrations aren't run when not needed.
This also meens you don't have to store anything anymore in
SharedPreferences.
In the migration you have to set all values of new columns when the type is not nullable, ...
Empty Strings can be inserted but only when setting convertColumnToNullable on the column

Transactions in GreenDao

I'm using GreenDao to store a lot of data, coming from a REST service.
A lot of my entities are connected with relations.
Everything works great, but tomorrow I have to implement a rocksolid workflow.
When I load my data I have to check if an error occurs.
If so, I have to make sure nothing is stored in the SQLite DB.
Normally I would work with transactions and rollback in case of an exception,
otherwise commit to the db.
For now I just use insertordelete to save an entity, everytime I created an object.
What would be the way to implement this?
On inserts and updates Greendao checks if there is a ongoing transaction. If that is the case greendao will not start a new transaction.
So the only thing to do is to start a transaction on your database and commit/rollback after your work is done or you notice an error. All inserts and updates will be in the same transaction which has benefits concerning data consistency and also on performance, since greendao will start new transactions with commit/rollback for every insert and update operation.
Summarized you can use code like this:
SQLiteDatabase db = dao.getDatabase();
db.beginTransaction();
try {
// do all your inserts and so on here.
db.setTransactionSuccessful();
} catch (Exception ex) {
} finally {
db.endTransaction();
}
I also tweaked my greendao a bit so that it doesn't cache inserted objects to get further performance and memoryusage benefits (since I insert a lot of data once and I only use very few data during runtime depending on user input). See this post.

Categories

Resources