realm/realm-swift

In memory realm loses data even though I am calling from same thread

Closed this issue · 4 comments

NOTE: This only happens using InMemoryRealm configuration something like Realm.Configuration(inMemoryIdentifier: "messagesRealm")

Goals

I am writing a cache protocol which I can implement later using any or multiple cache libraries (regardless of disk or memory), in this case it is Realm

Expected Results

Expectation was when the save function is called realm should successfully save the object in memory and when retrieved it should return it successfully or return all objects.

Actual Results

In memory realm is returning empty every time I insert something and try to fetch it (I am 100% sure it is inserting successfully and I will explain later why)

Steps for others to Reproduce

Reproduce the problem

  1. Clone the git repository in Code Sample
  2. Compile and run on a simulator/device (they both are giving the same results)
  3. In the Message field write anything
  4. Press Save
  5. Now press Refresh it should populate a list with data but it won't do it

What I have done

  • While reproducing the problem navigate to Realm -> InMemoryRealmCache.swift and setup a breakpoint at line 94.
  • Now run the application again and repeat step 3 to 4 the application should break after pressing Save
  • In the lldb write po realm.objects(MessageRealmEntity.self) and finally continue the application
  • Now press Refresh and the list will be populated No matter how many times you add and refresh it will work now 100%.

Video to show the problem and how to reproduce

realm-bug_nlFwK7Nu.mov

Code Sample

This is a git repository for a dummy application to reproduce the bug:

https://github.com/Muhand/InMemoryRealm-Bug

Version of Realm and Tooling

Realm framework version: RealmSwift (10.1.4)

Realm Object Server version: ?

Xcode version: Version 12.2 (12B45b)

iOS/OSX version: iOS 14.2

Dependency manager + version: ?

Few important notes

  • As can be seen in the project I am initializing Realm in every function in my wrapper this way I can avoid Realm accessed from incorrect thread issue.
  • However, to avoid realm being initialized on different threads I made sure to set a DispatchQueue in which they all are initialized and ran in.
  • I have tried making my Realm object to be static and living somewhere else but this is not scalable and if someone in the team called the functions from a different thread it can crash the app with the error Realm accessed from incorrect thread
  • The wrapper works 100% fine if I am using disk realm instead of memory
  • The wrapper is initialized only once so I don't have multiple instances of it
  • My first thinking is that Realm is going out of scope but then when I started to setup breakpoints like showing in the video it actually shifted my thinking.
  • Creating a strong reference causes error Realm accessed from incorrect thread which makes sense

I was able to solve the issue by creating a strong reference to realm (which I never use) and then I reinitialized realm in each function to avoid Realm accessed from incorrect thread not sure if this is the right way but at least it is a hack for now. I can see why Realm might go out of scope of there is no strong reference but in a use case like mine it doesn't really make sense to create an extra variable which I never get to use.

here is what I have done

final class RealmInMemoryCache {

    private let configuration: Realm.Configuration
    private let queue: DispatchQueue
    private let strongRealm: Realm     <-- Added variable

    init(_ configuration: Realm.Configuration) {
        self.queue = DispatchQueue(label: "inMemoryRealm", qos: .utility)
        self.configuration = configuration
        self.strongRealm = try! Realm(configuration: self.configuration)    <-- Initialized here
    }
}

and with every function in my class I do the following

...
        self.queue.async {
            guard let realm = try? Realm(configuration: self.configuration) else {
                completion(.failure(RealmInMemoryCacheError.realmIsNil))
                return
            }
...

I still don't understand why a breakpoint "solved" the issue except that xcode debugger might have created a strong reference to Realm when I write the command po realm.objects(...) in lldb that is my only thinking now.

Hi @Muhand !

Thank you for providing detailed information for your issue including a code example.

I have checked out your code example and could reproduce every step you described.

As for your question with the reference please see the documentation:
https://realm.io/docs/swift/latest/#in-memory-realms

Notice: When all in-memory Realm instances with a particular identifier go out of scope with no references,
all data in that Realm is deleted. We recommend holding onto a strong reference to any in-memory Realms 
during your app’s lifetime. (This is not necessary for on-disk Realms.)

Data not being held in the in-memory database is actually working as designed. You are basically creating a new realm every time you call

try? Realm(configuration: self.configuration)

Your approach of adding a strong reference is therefore the correct solution, you would want to pass the DispatchQueue you created for working with realm into its initialiser though.

Be aware though that doing this in the initialiser of RealmInMemoryCache like this

self.strongRealm = try! Realm(configuration: self.configuration, queue: self.queue)

will lead to a Realm opened from incorrect dispatch queue. error if done on another thread.

My suggested solution for your example would be:

private lazy var realm: Realm = {
  return try! Realm(configuration: self.configuration, queue: self.queue)
}()

No you can remove all occurrences of

guard let realm = try? Realm(configuration: self.configuration) else {
  completion(.failure(RealmInMemoryCacheError.realmIsNil))
  return
}

from your code and just access the member self.realm instead.

Does this solve your problem sufficiently?

Kind regards,
Dominic

Dominic,
Thanks for your suggestion, I will definitely implement it. That would solve my problem indeed.

Thanks again,
Muhand

Hi @Muhand, glad to hear your problem is solved!