Uploaded image for project: 'Realm Core'
  1. Realm Core
  2. RCORE-1379

BindingContext::did_change() is not called correctly when bootstrapping an outdated Flexible Sync Realm

    • Type: Icon: Bug Bug
    • Resolution: Cannot Reproduce
    • Priority: Icon: Major - P3 Major - P3
    • Q4-23FY
    • Affects Version/s: None
    • Component/s: None
    • None

      Scenario:

      1. Create a Flexible Sync Realm1 and write 1 object to it.
      2. Use the writeCopy API to copy Realm1 to Realm2.
      3. Realm1 writes 100 more objects to the server. The Realm2 copy is now missing these objects.
      4. Open Realm2 and listen to changes using a RealmChanged listener, not a collection listener.
      5. By observing logs we can see that Sync is writing changes to the Realm, but the realmChanged listener is not called.
      6. Creating a new SharedRealm while this is happening will correctly show the data is there.
      7. Using a CollectionListener on e.g. RealmResults will also report the correct result.

      It looks like we are failing to correctly call BindingContext::did_change() when integrating changes this way.

      Note, that as soon as Realm 2 is caught up, the RealmChanged listeners are correctly called. I.e. if you swap step 2 and 3, and then do further writes later (so both Realm 1 and Realm 2 are online), changes are correctly reported. Could this point to some problem when bootstrapping flexible data that are out of data with the server?

      The Kotlin unit test reproducing this is this one:

          @Test
          fun realmUpdatedWhileFlexibleSyncIsIntegratingChanges() = runBlocking {
              val flexApp = TestApp(
                  appName = io.realm.kotlin.test.mongodb.TEST_APP_FLEX,
                  builder = {
                      it.syncRootDirectory(PlatformUtils.createTempDir("flx-sync-"))
                  }
              )
              val section = Random.nextInt()
              val (email1, password1) = randomEmail() to "password1234"
              val (email2, password2) = randomEmail() to "password1234"
              val user1 = flexApp.createUserAndLogIn(email1, password1)
              val user2 = flexApp.createUserAndLogIn(email2, password2)
              val syncConfig1 = createFlexibleSyncConfig(
                  user = user1,
                  name = "sync1.realm",
                  initialSubscriptions = { realm: Realm ->
                      realm.query<FlexParentObject>("section = $0", section).subscribe()
                  }
              )
              val syncConfig2 = createFlexibleSyncConfig(
                  user = user2,
                  name = "sync2.realm",
                  initialSubscriptions = { realm: Realm ->
                      realm.query<FlexParentObject>("section = $0", section).subscribe()
                  }
              )
              // 1. Write first batch of sample data
              val realm1 = Realm.open(syncConfig1)
              realm1.write {
                  copyToRealm(FlexParentObject(section))
              }
              realm1.subscriptions.waitForSynchronization(30.seconds)
              realm1.syncSession.uploadAllLocalChanges(30.seconds)
      
              // 2. Copy the Realm with data to user 2
              realm1.writeCopyTo(syncConfig2)
      
              // 3. Write large amount of data to first Realm. This will not be in the Realm2 copy.
              repeat(100) {
                  realm1.write {
                      copyToRealm(FlexParentObject(section))
                  }
              }
      
              // 4. Open Realm 2 and wait for all changes to make its way to the Realm. We don't 100%
              // control how many updates we see, but we should see more than just 0 -> done.
              val realm2 = Realm.open(syncConfig2)
              val c = Channel<ArrayList<Long>>(1)
              async {
                  val updates: ArrayList<Long> = arrayListOf<Long>()
      // Listen to changes to a `Results`. This is correctly updated
      //            realm2.query<FlexParentObject>().count().asFlow()
      //                .transformWhile {
      //                    emit(it)
      //                    (it < 101)
      //                }
      //                .collect {
      //                    println("See updates: $it")
      //                    updates.add(it)
      //                }
      
                  // Listen to changes using C-API:
                  realm2.asFlow()
                      .transformWhile {
                          val size = it.realm.query<FlexParentObject>().count().find()
                          emit(size)
                          (size < 101)
                      }
                      .collect {
                          println("See updates: $it")
                          updates.add(it)
                      }
                      c.send(updates)
              }
      
              // 5. Listen to changes
              try {
                  withTimeout(60.seconds) {
                      val updates = c.receive()
                      // We should see more updates than just first and last update.
                      assertTrue(2 < updates.size, "Size is: ${updates.size}")
                      assertEquals(101, updates.last())
                  }
              } finally {
                  realm1.close()
                  realm2.close()
                  flexApp.close()
              }
              Unit
          }
      

            Assignee:
            jorgen.edelbo@mongodb.com Jørgen Edelbo
            Reporter:
            christian.melchior@mongodb.com Christian Melchior (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated:
              Resolved: