From 8ecb791a3e6e4387ba4beccfcd910b6b3a167cd6 Mon Sep 17 00:00:00 2001 From: Daniel Tabacaru Date: Wed, 14 Aug 2024 12:55:32 +0200 Subject: [PATCH] Clear incomplete bootstraps when the connection is established --- CHANGELOG.md | 2 +- src/realm/sync/noinst/client_impl_base.cpp | 12 +------ src/realm/sync/noinst/client_impl_base.hpp | 10 ++++++ test/object-store/sync/flx_sync.cpp | 38 ++++++++++++++++++++++ 4 files changed, 50 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 933f9009d0..4e2cd6a6c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ### Fixed * ([#????](https://github.com/realm/realm-core/issues/????), since v?.?.?) -* None. +* If the client disconnects while a bootstrap is in progresss, stale data from the previous bootstrap may be included when the client reconnects and the bootstrap is restarted. This can lead to objects stored in the database that do not match the actual state of the server and potentially leading to compensating writes. ([#7707](https://github.com/realm/realm-core/issues/7707), since v12.0.0) ### Breaking changes * None. diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp index eb57eafd45..96bbc2456d 100644 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ b/src/realm/sync/noinst/client_impl_base.cpp @@ -1296,7 +1296,7 @@ Session* Connection::find_and_validate_session(session_ident_type session_ident, logger.error("Bad session identifier in %1 message, session_ident = %2", message, session_ident); close_due_to_protocol_error( {ErrorCodes::SyncProtocolInvariantFailed, - util::format("Received message %1 for session iden %2 when that session never existed", message, + util::format("Received message %1 for session ident %2 when that session never existed", message, session_ident)}); } else { @@ -1709,16 +1709,6 @@ void Session::activate() REALM_ASSERT(!m_suspended); m_conn.one_more_active_unsuspended_session(); // Throws - try { - process_pending_flx_bootstrap(); // throws - } - catch (const IntegrationException& error) { - on_integration_failure(error); - } - catch (...) { - on_integration_failure(IntegrationException(exception_to_status())); - } - // Checks if there is a pending client reset handle_pending_client_reset_acknowledgement(); } diff --git a/src/realm/sync/noinst/client_impl_base.hpp b/src/realm/sync/noinst/client_impl_base.hpp index c6dd8f97ca..3b4b20ed7a 100644 --- a/src/realm/sync/noinst/client_impl_base.hpp +++ b/src/realm/sync/noinst/client_impl_base.hpp @@ -1398,6 +1398,16 @@ inline void ClientImpl::Session::connection_established(bool fast_reconnect) // the bind messsage call_debug_hook(SyncClientHookEvent::SessionConnected); + try { + process_pending_flx_bootstrap(); // throws + } + catch (const IntegrationException& error) { + on_integration_failure(error); + } + catch (...) { + on_integration_failure(IntegrationException(exception_to_status())); + } + if (!m_suspended) { // Ready to send BIND message enlist_to_send(); // Throws diff --git a/test/object-store/sync/flx_sync.cpp b/test/object-store/sync/flx_sync.cpp index 9f648c44d7..685554ab7f 100644 --- a/test/object-store/sync/flx_sync.cpp +++ b/test/object-store/sync/flx_sync.cpp @@ -5174,6 +5174,44 @@ TEST_CASE("flx: no upload during bootstraps", "[sync][flx][bootstrap][baas]") { wait_for_download(*realm); } +TEST_CASE("flx: bootstrap is properly applied when the connection is reestablished", "[sync][flx][bootstrap][baas]") { + FLXSyncTestHarness harness("flx_bootstrap", {g_large_array_schema, {"queryable_int_field"}}); + auto& app_session = harness.session().app_session(); + REQUIRE(app_session.admin_api.patch_app_settings(app_session.server_app_id, + {{"sync", {{"num_objects_before_bootstrap_flush", 1}}}})); + REQUIRE(app_session.admin_api.patch_app_settings( + app_session.server_app_id, {{"sync", {{"qbs_download_changeset_soft_max_byte_size", 1000}}}})); + + fill_large_array_schema(harness); + auto config = harness.make_test_file(); + bool once = false; + config.sync_config->on_sync_client_event_hook = [&once](std::weak_ptr, + const SyncClientHookData& data) { + if (data.query_version != 1) { + return SyncClientHookAction::NoAction; + } + if (data.event == SyncClientHookEvent::BootstrapMessageProcessed && !once) { + once = true; + return SyncClientHookAction::TriggerReconnect; + } + // The batch of changesets added to the PendingBoostrapStore before disconnect + // were removed when the connection was reestablished. + // There are 5 changesets in 5 download messages and one additional download message + // with an empty changeset (as per server design). + if (data.event == SyncClientHookEvent::BootstrapProcessed) { + CHECK(data.num_changesets == 6); + } + return SyncClientHookAction::NoAction; + }; + + auto realm = Realm::get_shared_realm(config); + auto table = realm->read_group().get_table("class_TopLevel"); + auto new_subs = realm->get_latest_subscription_set().make_mutable_copy(); + new_subs.insert_or_assign(Query(table)); + auto subs = new_subs.commit(); + subs.get_state_change_notification(sync::SubscriptionSet::State::Complete).get(); +} + } // namespace realm::app #endif // REALM_ENABLE_AUTH_TESTS