diff --git a/CHANGELOG.md b/CHANGELOG.md index 15931110339..5c12aea806b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ * Calling `Realm::close()` or `Realm::invalidate()` from the async write callbacks could result in crashes (since v11.8.0). * Asynchronous writes did not work with queue-confined Realms (since v11.8.0). * Releasing all references to a Realm while an asynchronous write was in progress would sometimes result in use-after-frees (since v11.8.0). +* Fixed a fatal sync error "Automatic recovery failed" during DiscardLocal client reset if the reset notifier callbacks were not set to something. ([#5223](https://github.com/realm/realm-core/issues/5223), since v11.5.0) * Throwing exceptions from asynchronous write callbacks would result in crashes or the Realm being in an invalid state (since v11.8.0). ### Breaking changes diff --git a/src/realm/object-store/sync/sync_session.cpp b/src/realm/object-store/sync/sync_session.cpp index 57eda30827d..989d921cac5 100644 --- a/src/realm/object-store/sync/sync_session.cpp +++ b/src/realm/object-store/sync/sync_session.cpp @@ -529,6 +529,46 @@ void SyncSession::create_sync_session() do_create_sync_session(); } +sync::Session::Config::ClientReset make_client_reset_config(SyncConfig& session_config, DBRef&& fresh_copy) +{ + sync::Session::Config::ClientReset config; + config.discard_local = (session_config.client_resync_mode == ClientResyncMode::DiscardLocal); + config.notify_after_client_reset = [&session_config](std::string local_path, VersionID previous_version) { + REALM_ASSERT(!local_path.empty()); + SharedRealm frozen_before, active_after; + if (auto local_coordinator = RealmCoordinator::get_existing_coordinator(local_path)) { + auto local_config = local_coordinator->get_config(); + active_after = local_coordinator->get_realm(local_config, util::none); + local_config.scheduler = nullptr; + frozen_before = local_coordinator->get_realm(local_config, previous_version); + REALM_ASSERT(active_after); + REALM_ASSERT(!active_after->is_frozen()); + REALM_ASSERT(frozen_before); + REALM_ASSERT(frozen_before->is_frozen()); + } + if (session_config.notify_after_client_reset) { + session_config.notify_after_client_reset(frozen_before, active_after); + } + }; + config.notify_before_client_reset = [&session_config](std::string local_path) { + REALM_ASSERT(!local_path.empty()); + SharedRealm frozen_local; + Realm::Config local_config; + if (auto local_coordinator = RealmCoordinator::get_existing_coordinator(local_path)) { + local_config = local_coordinator->get_config(); + local_config.scheduler = nullptr; + frozen_local = local_coordinator->get_realm(local_config, VersionID()); + REALM_ASSERT(frozen_local); + REALM_ASSERT(frozen_local->is_frozen()); + } + if (session_config.notify_before_client_reset) { + session_config.notify_before_client_reset(frozen_local); + } + }; + config.fresh_copy = std::move(fresh_copy); + return config; +} + void SyncSession::do_create_sync_session() { sync::Session::Config session_config; @@ -558,38 +598,7 @@ void SyncSession::do_create_sync_session() session_config.custom_http_headers = m_config.custom_http_headers; if (m_force_client_reset) { - sync::Session::Config::ClientReset config; - config.discard_local = (m_config.client_resync_mode == ClientResyncMode::DiscardLocal); - config.notify_after_client_reset = [this](std::string local_path, VersionID previous_version) { - REALM_ASSERT(!local_path.empty()); - SharedRealm frozen_before, active_after; - if (auto local_coordinator = RealmCoordinator::get_existing_coordinator(local_path)) { - auto local_config = local_coordinator->get_config(); - active_after = local_coordinator->get_realm(local_config, util::none); - local_config.scheduler = nullptr; - frozen_before = local_coordinator->get_realm(local_config, previous_version); - REALM_ASSERT(active_after); - REALM_ASSERT(!active_after->is_frozen()); - REALM_ASSERT(frozen_before); - REALM_ASSERT(frozen_before->is_frozen()); - } - m_config.notify_after_client_reset(frozen_before, active_after); - }; - config.notify_before_client_reset = [this](std::string local_path) { - REALM_ASSERT(!local_path.empty()); - SharedRealm frozen_local; - Realm::Config local_config; - if (auto local_coordinator = RealmCoordinator::get_existing_coordinator(local_path)) { - local_config = local_coordinator->get_config(); - local_config.scheduler = nullptr; - frozen_local = local_coordinator->get_realm(local_config, VersionID()); - REALM_ASSERT(frozen_local); - REALM_ASSERT(frozen_local->is_frozen()); - } - m_config.notify_before_client_reset(frozen_local); - }; - config.fresh_copy = std::move(m_client_reset_fresh_copy); - session_config.client_reset_config = std::move(config); + session_config.client_reset_config = make_client_reset_config(m_config, std::move(m_client_reset_fresh_copy)); m_force_client_reset = false; } diff --git a/test/object-store/sync/client_reset.cpp b/test/object-store/sync/client_reset.cpp index 1c55596982b..2b5ae1e2716 100644 --- a/test/object-store/sync/client_reset.cpp +++ b/test/object-store/sync/client_reset.cpp @@ -341,6 +341,14 @@ TEST_CASE("sync: client reset", "[client reset]") { } } + SECTION("can be reset without notifiers") { + local_config.sync_config->notify_before_client_reset = nullptr; + local_config.sync_config->notify_after_client_reset = nullptr; + make_reset(local_config, remote_config)->run(); + REQUIRE(before_callback_invoctions == 0); + REQUIRE(after_callback_invocations == 0); + } + SECTION("an interrupted reset can recover on the next session") { struct SessionInterruption : public std::runtime_error { using std::runtime_error::runtime_error;