From 9252ccc6c769c26709bc8b4969bfc61d4cf7bae9 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Wed, 7 Aug 2024 16:03:38 -0700 Subject: [PATCH] Actually check for unuplaoded changes in no_pending_local_changes() We can have local changesets stored which have already been uploaded and acknoledged by the server, so checking all of the changesets is incorrect. We need to instead only check changesets for versions after the current position of the upload cursor. --- CHANGELOG.md | 1 + src/realm/sync/noinst/client_history_impl.cpp | 7 +++- test/object-store/realm.cpp | 37 +++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d0f451240..22deba5880 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * Fixed conflict resolution bug related to ArrayErase and Clear instructions, which could sometimes cause an "Invalid prior_size" exception to prevent synchronization ([#7893](https://github.com/realm/realm-core/issues/7893), since v14.8.0). * Fixed bug which would prevent eventual consistency during conflict resolution. Affected clients would experience data divergence and potentially consistency errors as a result. ([PR #7955](https://github.com/realm/realm-core/pull/7955), since v14.8.0) * Fixed issues loading the native Realm libraries on Linux ARMv7 systems when they linked against our bundled OpenSSL resulting in errors like `unexpected reloc type 0x03`. ([#7947](https://github.com/realm/realm-core/issues/7947), since v14.1.0) +* `Realm::convert()` would sometimes incorrectly throw an exception claiming that there were unuploaded local changes when the source Realm is a synchronized Realm ([#7966](https://github.com/realm/realm-core/issues/7966), since v10.7.0). ### Breaking changes * None. diff --git a/src/realm/sync/noinst/client_history_impl.cpp b/src/realm/sync/noinst/client_history_impl.cpp index 3d00f64353..fbd51d8890 100644 --- a/src/realm/sync/noinst/client_history_impl.cpp +++ b/src/realm/sync/noinst/client_history_impl.cpp @@ -1064,7 +1064,12 @@ void ClientHistory::trim_sync_history() bool ClientHistory::no_pending_local_changes(version_type version) const { ensure_updated(version); - for (size_t i = 0; i < sync_history_size(); i++) { + size_t base_version = 0; + auto upload_client_version = + version_type(m_arrays->root.get_as_ref_or_tagged(s_progress_upload_client_version_iip).get_as_int()); + if (upload_client_version > m_sync_history_base_version) + base_version = size_t(upload_client_version - m_sync_history_base_version); + for (size_t i = base_version; i < sync_history_size(); i++) { if (m_arrays->origin_file_idents.get(i) == 0) { std::size_t pos = 0; BinaryData chunk = m_arrays->changesets.get_at(i, pos); diff --git a/test/object-store/realm.cpp b/test/object-store/realm.cpp index bfe434f856..8228be8ec8 100644 --- a/test/object-store/realm.cpp +++ b/test/object-store/realm.cpp @@ -1647,6 +1647,43 @@ TEST_CASE("SharedRealm: convert", "[sync][pbs][convert]") { // Check that the data also exists in the new realm REQUIRE(local_realm2->read_group().get_table("class_object")->size() == 1); } + + SECTION("synced realm must be fully uploaded") { + auto realm = Realm::get_shared_realm(sync_config1); + realm->sync_session()->pause(); + realm->begin_transaction(); + realm->read_group().get_table("class_object")->create_object_with_primary_key(0); + realm->commit_transaction(); + + SyncTestFile sync_config2(tsm, "default"); + sync_config2.schema = schema; + REQUIRE_EXCEPTION(realm->convert(sync_config2), IllegalOperation, + "All client changes must be integrated in server before writing copy"); + + realm->sync_session()->resume(); + wait_for_upload(*realm); + REQUIRE_NOTHROW(realm->convert(sync_config2)); + } + + SECTION("can convert synced realm from within upload complete callback") { + auto realm = Realm::get_shared_realm(sync_config1); + realm->sync_session()->pause(); + realm->begin_transaction(); + realm->read_group().get_table("class_object")->create_object_with_primary_key(0); + realm->commit_transaction(); + + SyncTestFile sync_config2(tsm, "default"); + sync_config2.schema = schema; + auto pf = util::make_promise_future(); + realm->sync_session()->wait_for_upload_completion([&](Status) { + sync_config1.scheduler = util::Scheduler::make_dummy(); + auto realm = Realm::get_shared_realm(sync_config1); + REQUIRE_NOTHROW(realm->convert(sync_config2)); + pf.promise.emplace_value(); + }); + realm->sync_session()->resume(); + pf.future.get(); + } } TEST_CASE("SharedRealm: convert - embedded objects", "[sync][pbs][convert][embedded objects]") {