Can Realm's executeTransaction be safely retried without duplicating writes?
0 reputation · 05 May 2022, 16:34 UTC
0 reputation · 05 May 2022, 16:34 UTC
When a write operation fails inside Realm.executeTransaction(), the transaction is rolled back and the method returns false, allowing the caller to retry the same intent. Because Realm uses MVCC, readers see a consistent snapshot and committed changes are invisible until the transaction succeeds.
The goal is to confirm that a retry does not create duplicate objects, even if the retry occurs after a delay that may leave the MVCC snapshot stale. Considerations include whether a manual Realm.refresh() is required before retrying and how to structure the retry loop to guarantee idempotent writes.
Does Realm automatically refresh the MVCC snapshot on each retry, or must the caller invoke Realm.refresh() explicitly? What retry pattern ensures that application‑level logic does not inadvertently produce duplicate rows when using executeTransaction? Are there any scenarios where a delayed retry could still result in orphaned data despite the rollback mechanism?
35275 reputation · 05 May 2022, 23:12 UTC
Yes – if you retry a Realm.executeTransaction() after a failure, Realm will not duplicate writes **provided the transaction body itself is idempotent or checks for existing state**. The transaction is atomic: if it throws before the commit, the entire write is rolled back. Realm automatically starts a fresh MVCC snapshot for each new transaction, so you do not need to call realm.refresh() before a retry.
The only time a retry can produce duplicates is when:
create without a primary key or increment on a counter).realm.executeTransaction().
Example – idempotent insert:
realm.executeTransaction { r ->
// Use a unique primary key or idempotency key
val existing = r.where(MyObject::class.java)
.equalTo("id", newId)
.findFirst()
if (existing == null) {
val obj = r.createObject(MyObject::class.java, newId)
obj.name = "foo"
} else {
existing.name = "foo" // update instead of duplicate
}
}
realm.refresh()Realm automatically refreshes to the latest snapshot when a new transaction starts. realm.refresh() is only necessary if you are observing the same snapshot across multiple threads or you need to force a refresh in a long‑running background task. For a simple retry loop, you can skip it.
Does your transaction body perform any external I/O (network, file system, etc.) after the Realm write? If so, we may need to adjust the retry strategy to avoid duplicate side effects.
Use comments to ask for clarification. Post a solution as an answer.
35,275 reputation · 05 May 2022, 17:59 UTC
When Realm.executeTransaction() returns false, the Realm instance remains bound to the thread that opened it. If you retry on the same thread you can reuse the instance; however, if the retry occurs after switching threads (e.g., from a background worker back to the UI thread), you must obtain a fresh Realm instance for that thread before calling executeTransaction again. Failing to do so leads to an IllegalStateException because Realm objects are thread‑confined, not to duplicate writes.