Realm Swift Tutorial failing offline-first usage

I was able to clone, build, and go -

2021-02-09 12:08:16.899494-0700 HangingApp[91164:6730907] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed
2021-02-09 12:08:17.827458-0700 HangingApp[91164:6730911] Version 10.5.2 of Realm is now available: https://github.com/realm/realm-cocoa/blob/v10.5.2/CHANGELOG.md
2021-02-09 12:08:17.835052-0700 HangingApp[91164:6730958] Sync: Connection[1]: Session[1]: client_reset_config = false, Realm exists = true, async open = false, client reset = false
2021-02-09 12:08:17.942314-0700 HangingApp[91164:6730958] Sync: Connection[1]: Connected to endpoint '34.227.4.145:443' (from '10.1.131.38:58121')
2021-02-09 12:08:18.892592-0700 HangingApp[91164:6730958] Sync: Connection[2]: Session[2]: client_reset_config = false, Realm exists = true, async open = false, client reset = false
2021-02-09 12:08:18.999104-0700 HangingApp[91164:6730958] Sync: Connection[2]: Connected to endpoint '34.227.4.145:443' (from '10.1.131.38:58123')
here
2021-02-09 12:08:19.745929-0700 HangingApp[91164:6730787] Login success

@Richard_Krueger
@Ian_Ward

Me too. Not only did your project work. I plucked the code out of your app and made a macOS project and it connects over and over using the same code, no problem at all.

021-02-09 14:33:32.035666-0500 RealmHangTest[9821:1099884] Sync: Connection[1]: Session[1]: client_reset_config = false, Realm exists = true, async open = false, client reset = false

2021-02-09 14:33:32.086925-0500 RealmHangTest[9821:1099884] Sync: Connection[1]: Connected to endpoint ‘3.210.32.164:443’ (from ‘192.168.123.207:xxxxx’)

2021-02-09 14:33:32.655541-0500 RealmHangTest[9821:1099884] Sync: Connection[2]: Session[2]: client_reset_config = false, Realm exists = true, async open = false, client reset = false

2021-02-09 14:33:32.714108-0500 RealmHangTest[9821:1099884] Sync: Connection[2]: Connected to endpoint ‘3.210.32.164:443’ (from ‘192.168.123.207:52134’)

2021-02-09 14:33:33.141745-0500 RealmHangTest[9821:1099520] Login success

No perhaps a location issue related to global - like I was having ?

ah cool just wondering whether 12.4 support was coming. :+1:

It’s 20:57 EST, and right now it is working like a champ, i.e. no hanging whatsoever. As I said in a previous post, this issue seems to be intermittent. It started happening last Friday night and throughout the weekend. @Luccas_Clezar tested the same code and it hung for him too - that was two days ago. There are times when asyncOpen() just does not come back for a few minutes, which can be very disconcerting as it is usually the first thing that happens right after a login.

I ended changing my code to a regular sync open, which comes back immediately - so I will probably stick with that as a strategy moving forward.

Hi @Richard_Krueger,

I’ve done the same with my own refactoring, but this leaves me with another detail to contend with then.
Although the UI/UX continues onward, how can I force trigger the sync engine in the background to start its work?

Doesn’t it start automatically as soon as you open the realm ? If you have sync logging on you should see the sync connection

What if I open it on a background thread that doesn’t have a run loop event?
Or the app fluctuates between offline and online, which doesn’t always instantly trigger a sync (due to perhaps more multithreading).

There’s always a UI element (button or pull-to-refresh) in my app that gives the user control to trigger a sync anytime they want, instead of waiting on the app.

This is what I experienced when using the Realm App global deployment option - have you tried using the local option so see if you get a more consistent performance.

We always open the first Realm on the main thread - even if the app doesn’t use it. Then if we need some long task we open one or more realms on background threads. Say to produce reports or import data.

There is no ability to manually trigger a sync as far as I am aware - nor should it be necessary. Usually updates come through nearly instantly - fast enough for collaboration between two users in our experience.

Well that is with the current Realm Cloud - we haven’t done comprehensive testing on MongoDB Realm yet but I expect it should also be very quick.

I am not 100% sure about whether you need to open the Realm on a runloop thread for sync to be started - I don’t think so - at least I don’t recall running into this problem with our data migration app which does everything from a background thread and syncs just fine.

I should qualify that - it is macOS so may behave differently to iOS in this regards.

1 Like

@Duncan_Groenewald you can force a run loop on a background thread, I did this in my Storage example, because I need a background thread to service the upload request. You create the background thread as follows:

        if let uid = RealmManager.shared.currentUserId {
            if self.uploadThread==nil {
                self.uploadThread = Thread(target: self, selector: #selector(uploadThreadEntryPoint(uid:)), object: uid)
                self.uploadThread!.start()
            }
            
            if let uploadThread = self.uploadThread {
                perform(#selector(setupBackground), on: uploadThread, with: nil, waitUntilDone: false, modes: [RunLoop.Mode.common.rawValue])
            }
        }

Then you define the tread entry point like this

    @objc func uploadThreadEntryPoint(uid: String) {
        autoreleasepool {
            Thread.current.name = "CosyncUploadThread_\(uid)"
            let runLoop = RunLoop.current
            runLoop.add(NSMachPort(), forMode: RunLoop.Mode.default)
            runLoop.run()
        }
    }

This runloop is running on your background thread. Then your servicing function would like this:

    @objc func setupBackground() -> Void {
        if  let user = RealmManager.shared.app.currentUser,
            let uid = RealmManager.shared.currentUserId,
            let sessionId = AssetManager.shared.sessionId {
            
            self.userRealm = try! Realm(configuration: user.configuration(partitionValue: "user_id=\(uid)"))
            
            if let realm = self.userRealm {
                let results = realm.objects(CosyncAssetUpload.self)
                    .filter("uid == '\(uid)' && sessionId=='\(sessionId)' && status=='initialized'")
                
                self.notificationToken = results.observe { [self] (changes: RealmCollectionChange) in
            
                    switch changes {
                    case .initial:
                        for assetUpload in results {
                            self.uploadAsset(assetUpload: assetUpload)
                        }
                        
                    case .update( let results, _, _, _):
                        for assetUpload in results {
                            self.uploadAsset(assetUpload: assetUpload)
                        }
                        
                    case .error(let error):
                        // An error occurred while opening the Realm file on the background worker thread
                        fatalError("\(error)")
                    }
                }
            }
        }
    }

For more information, you can check out a Medium article I wrote about multi-threading in Realm.

The main rule to remember about threading in Realm, is that whatever happens on a thread Realm-wise must stay on the thread - it’s the Vegas rule of threading. Otherwise, MongoDB Realm is perfectly thread safe and performs flawlessly.

There is no ability to manually trigger a sync as far as I am aware - nor should it be necessary

I don’t want to be at the whim of when Realm decides to sync. As it can take a while for it to figure out that it’s back online and then try to sync. I’ve tested delays of over a minute.

As a user in the field that’s late for dinner and just got back to my car with a slow data connection, the last thing I want is to wait for my app to figure it out and sync. Machines should wait on us, not the other way around.

Looks like I’ll be making a request in the SDK forum.

See Thomas comment above (excerpt below) re using the network reachability observer - you might want to see why it is taking a couple of minutes to start syncing since it should start as soon as the network becomes available. Set sync logging to “all” on the client.

When connecting fails we do have automatic backoff to avoid killing the device’s battery, but we use a network reachability observer to immediately reconnect when the OS notifies us that the networking state has changed.

@Sebastian_Dwornik and @Duncan_Groenewald

Forgive me for asking but isn’t some of this discussion focusing on circumventing a built in functionality of MongoDB Realm?

The docs and developers recommend using asyncOpen() when first connecting so why would we want to circumvent it? (Given it’s not working for you, but that leads me to think there’s external influence involved)

It doesn’t matter which way you open the realm - happy to stand corrected by anyone from Realm on this.

It really depends on your use case. For our app there is no need to open with asyncOpen() unless it is the first time the app is being opened since it needs to download seed data and the app must wait until that is downloaded before it can be used.

Other than that opening the realm synchronously is fine and preferable as we have found out with all the working from home where networks are not very reliable.

If the user is offline then using openAsync() will cause the app to wait until the call returns - depending on the timeout value I guess. What is the point since if the app is offline it can’t sync anyway.

So in the spirit of offline first only use openAsync() when it is critical for the app to complete the download before it becomes available to the user.

As I said - happy to be corrected on this but this seems to work just fine for our users.

The docs team is working on improving the guidance around asyncOpen() to be less “You must use asyncOpen()” and more “Here’s when you want to use asyncOpen()” with a hopefully clear message that it’s fine not to use it the rest of the time.

WRT to forcing sync to reconnect, you can call realm.syncSession.suspend(); realm.syncSession.resume(), although that doesn’t do exactly what you’d want (as it forces a disconnect even if it’s already connected). I don’t think there’s any reason for us not to expose a reconnect() function as the Java SDK does; our goal is to not need it and just do a good enough job automatically but it’s not hard to imagine scenarios where it’s useful (e.g. if the device has a very flakey connection we usually don’t want to kill the battery spamming connection attempts, unless the user is mashing a refresh button because they really do need the data ASAP).

3 Likes

Actually there is one thing you want to watch out for when using Realm() to open the first time.

If you change the schema then the call to Realm() will block while any migration is run and if you are doing this on the main thread for the first time then your app may become unresponsive if the migration takes a long time to run.

I just realised we make our first call when the app is restarted but after it has already been initialised using DispatchQueue.global().async{} with a callback and once this returns we open the main thread realm and retain that for the life of the application. That way we avoid blocking the main thread.

Being a macOS app we get the umbrella of death if there is a schema change that requires a migration. Bear in mind changing from SDK v4 to v5 or v10 causes a migration as well even if no schema changes.

Hope this helps.

1 Like

Hello @Sebastian_Dwornik ,

I’m late to this thread , like you am coming from a project that we tried Firestore with but found it wanting when offline.

Did you finally get your app working off-line as required/expected ?

@John_Kan

Welcome to the forums. This is fairly old thread.

Keep in mind that Firestore is not an offline database, it’s Online First with offline persistence for short internet outages. Realm is a very different platform with Offline First being the priority.

If you have a specific question about coding or the platform, I would suggest searching the forums or StackOverflow first, and if you don’t find a suitable answer, create a new thread so we can take a look!

Hi @Jay , hey thanks for replying,

Yes, so We discovered after going a fair way down the proof of concept route.

We were experiencing what looked like a problem this thread was discussing , but after a few days of trying to identify when our app hung on re-start we finally found another discussion on the interweb that pointed us to the problem and the solution.

In our case We were just unlucky to have started our Realm project using a version of Realm that would not start up due to an expired authentication token, which expires after 30 minutes.

Upgrading to the latest version of Realm corrected this behaviour and it now works exactly as expected.

Thanks

1 Like