Skip to content

Commit 25212bc

Browse files
author
Michael Wilkerson-Barker
authored
RCORE-1386 Track client reset reason in table that detects client reset cycles (#7649)
* Broke out the client reset error and action storage from PR #7542 * Removed client reset recovery_allowed flag and other updates from review * Updated pending_client_reset store to use the schema metadata tables * Fixed pausing a session does not hold the DB open test * Moved ownership of reset store to SessionWrapper * Fixed migration test crash - need to save client reset error in handle fresh realm downloaded * Updated PendingResetStore to be static functions instead of an initialized object; updates from review * Make ClientReset::error no longer optional; fixed subscriptions tests * updated changelog after release * updates from review
1 parent ae3b22a commit 25212bc

29 files changed

+981
-436
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
### Enhancements
44
* <New feature description> (PR [#????](https://github.com/realm/realm-core/pull/????))
5+
* Report the originating error that caused a client reset to occur. ([#6154](https://github.com/realm/realm-core/issues/6154))
56

67
### Fixed
78
* <How do the end-user experience this issue? what was the impact?> ([#????](https://github.com/realm/realm-core/issues/????), since v?.?.?)
@@ -20,6 +21,7 @@
2021
* Work around a bug in VC++ that resulted in runtime errors when running the tests in a debug build (#[7741](https://github.com/realm/realm-core/issues/7741)).
2122
* Refactor `sync::Session` to eliminate the bind() step of session creation ([#7609](https://github.com/realm/realm-core/pull/7609)).
2223
* Add ScopeExitFail which only calls the handler if exiting the scope via an uncaught exception ([#7609](https://github.com/realm/realm-core/pull/7609)).
24+
* Add the originating error and server requests action that caused a client reset to occur to the client reset tracking metadata storage. ([PR #7649](https://github.com/realm/realm-core/pull/7649))
2325

2426
----------------------------------------------
2527

Package.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ let notSyncServerSources: [String] = [
121121
"realm/sync/noinst/compact_changesets.cpp",
122122
"realm/sync/noinst/migration_store.cpp",
123123
"realm/sync/noinst/pending_bootstrap_store.cpp",
124+
"realm/sync/noinst/pending_reset_store.cpp",
124125
"realm/sync/noinst/protocol_codec.cpp",
125126
"realm/sync/noinst/sync_metadata_schema.cpp",
126127
"realm/sync/noinst/sync_schema_migration.cpp",

src/realm/db.hpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,22 @@ inline int DB::get_file_format_version() const noexcept
697697
return m_file_format_version;
698698
}
699699

700+
inline std::ostream& operator<<(std::ostream& os, const DB::TransactStage& stage)
701+
{
702+
switch (stage) {
703+
case DB::TransactStage::transact_Ready:
704+
return os << "transact_Ready";
705+
case DB::TransactStage::transact_Reading:
706+
return os << "transact_Reading";
707+
case DB::TransactStage::transact_Frozen:
708+
return os << "transact_Frozen";
709+
case DB::TransactStage::transact_Writing:
710+
return os << "transact_Writing";
711+
}
712+
REALM_UNREACHABLE();
713+
}
714+
715+
700716
} // namespace realm
701717

702718
#endif // REALM_DB_HPP

src/realm/object-store/sync/sync_session.cpp

Lines changed: 47 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -422,17 +422,15 @@ void SyncSession::update_error_and_mark_file_for_deletion(SyncError& error, Shou
422422
}
423423
}
424424

425-
void SyncSession::download_fresh_realm(sync::ProtocolErrorInfo::Action server_requests_action)
425+
void SyncSession::download_fresh_realm(const sync::SessionErrorInfo& error_info)
426426
{
427427
// first check that recovery will not be prevented
428-
if (server_requests_action == sync::ProtocolErrorInfo::Action::ClientResetNoRecovery) {
428+
if (error_info.server_requests_action == sync::ProtocolErrorInfo::Action::ClientResetNoRecovery) {
429429
auto mode = config(&SyncConfig::client_resync_mode);
430430
if (mode == ClientResyncMode::Recover) {
431431
handle_fresh_realm_downloaded(
432-
nullptr,
433-
{ErrorCodes::RuntimeError,
434-
"A client reset is required but the server does not permit recovery for this client"},
435-
server_requests_action);
432+
nullptr, {ErrorCodes::RuntimeError,
433+
"A client reset is required but the server does not permit recovery for this client"});
436434
return;
437435
}
438436
}
@@ -469,14 +467,15 @@ void SyncSession::download_fresh_realm(sync::ProtocolErrorInfo::Action server_re
469467
catch (...) {
470468
// Failed to open the fresh path after attempting to delete it, so we
471469
// just can't do automatic recovery.
472-
handle_fresh_realm_downloaded(nullptr, exception_to_status(), server_requests_action);
470+
handle_fresh_realm_downloaded(nullptr, exception_to_status());
473471
return;
474472
}
475473

476474
util::CheckedLockGuard state_lock(m_state_mutex);
477475
if (m_state != State::Active) {
478476
return;
479477
}
478+
480479
RealmConfig fresh_config;
481480
{
482481
util::CheckedLockGuard config_lock(m_config_mutex);
@@ -511,7 +510,7 @@ void SyncSession::download_fresh_realm(sync::ProtocolErrorInfo::Action server_re
511510
using SubscriptionState = sync::SubscriptionSet::State;
512511
fresh_sub.get_state_change_notification(SubscriptionState::Complete)
513512
.then([=](SubscriptionState) -> util::Future<sync::SubscriptionSet> {
514-
if (server_requests_action != sync::ProtocolErrorInfo::Action::MigrateToFLX) {
513+
if (error_info.server_requests_action != sync::ProtocolErrorInfo::Action::MigrateToFLX) {
515514
return fresh_sub;
516515
}
517516
if (!self->m_migration_store->is_migration_in_progress()) {
@@ -527,7 +526,7 @@ void SyncSession::download_fresh_realm(sync::ProtocolErrorInfo::Action server_re
527526
fresh_sync_session->m_migration_store->create_subscriptions(*fresh_sub_store, *query_string);
528527
return fresh_sub_store->get_latest()
529528
.get_state_change_notification(SubscriptionState::Complete)
530-
.then([=](SubscriptionState) {
529+
.then([fresh_sub_store](SubscriptionState) {
531530
return fresh_sub_store->get_latest();
532531
});
533532
})
@@ -536,29 +535,32 @@ void SyncSession::download_fresh_realm(sync::ProtocolErrorInfo::Action server_re
536535
// it immediately
537536
fresh_sync_session->force_close();
538537
if (subs.is_ok()) {
539-
self->handle_fresh_realm_downloaded(db, Status::OK(), server_requests_action,
540-
std::move(subs.get_value()));
538+
self->handle_fresh_realm_downloaded(db, std::move(error_info), std::move(subs.get_value()));
541539
}
542540
else {
543-
self->handle_fresh_realm_downloaded(nullptr, subs.get_status(), server_requests_action);
541+
self->handle_fresh_realm_downloaded(nullptr, std::move(subs.get_status()));
544542
}
545543
});
546544
}
547545
else { // pbs
548-
fresh_sync_session->wait_for_download_completion([=, weak_self = weak_from_this()](Status s) {
546+
fresh_sync_session->wait_for_download_completion([=, weak_self = weak_from_this()](Status status) {
549547
// Keep the sync session alive while it's downloading, but then close
550548
// it immediately
551549
fresh_sync_session->force_close();
552550
if (auto strong_self = weak_self.lock()) {
553-
strong_self->handle_fresh_realm_downloaded(db, s, server_requests_action);
551+
if (status.is_ok()) {
552+
strong_self->handle_fresh_realm_downloaded(db, std::move(error_info));
553+
}
554+
else {
555+
strong_self->handle_fresh_realm_downloaded(nullptr, std::move(status));
556+
}
554557
}
555558
});
556559
}
557560
fresh_sync_session->revive_if_needed();
558561
}
559562

560-
void SyncSession::handle_fresh_realm_downloaded(DBRef db, Status status,
561-
sync::ProtocolErrorInfo::Action server_requests_action,
563+
void SyncSession::handle_fresh_realm_downloaded(DBRef db, StatusWith<sync::SessionErrorInfo> error_info,
562564
std::optional<sync::SubscriptionSet> new_subs)
563565
{
564566
util::CheckedUniqueLock lock(m_state_mutex);
@@ -569,15 +571,15 @@ void SyncSession::handle_fresh_realm_downloaded(DBRef db, Status status,
569571
// - unable to write the fresh copy to the file system
570572
// - during download of the fresh copy, the fresh copy itself is reset
571573
// - in FLX mode there was a problem fulfilling the previously active subscription
572-
if (!status.is_ok()) {
573-
if (status == ErrorCodes::OperationAborted) {
574+
if (!error_info.is_ok()) {
575+
if (error_info.get_status() == ErrorCodes::OperationAborted) {
574576
return;
575577
}
576578
lock.unlock();
577579

578580
sync::SessionErrorInfo synthetic(
579581
Status{ErrorCodes::AutoClientResetFailed,
580-
util::format("A fatal error occurred during client reset: '%1'", status.reason())},
582+
util::format("A fatal error occurred during client reset: '%1'", error_info.get_status())},
581583
sync::IsFatal{true});
582584
handle_error(synthetic);
583585
return;
@@ -593,9 +595,12 @@ void SyncSession::handle_fresh_realm_downloaded(DBRef db, Status status,
593595
// that moving to the inactive state doesn't clear them - they will be
594596
// re-registered when the session becomes active again.
595597
{
596-
m_server_requests_action = server_requests_action;
597598
m_client_reset_fresh_copy = db;
598599
CompletionCallbacks callbacks;
600+
// Save the client reset error for when the original sync session is revived
601+
REALM_ASSERT(error_info.is_ok()); // required if we get here
602+
m_client_reset_error = std::move(error_info.get_value());
603+
599604
std::swap(m_completion_callbacks, callbacks);
600605
// always swap back, even if advance_state throws
601606
auto guard = util::make_scope_exit([&]() noexcept {
@@ -607,11 +612,13 @@ void SyncSession::handle_fresh_realm_downloaded(DBRef db, Status status,
607612
});
608613
// Do not cancel the notifications on subscriptions.
609614
bool cancel_subscription_notifications = false;
615+
bool is_migration =
616+
m_client_reset_error->server_requests_action == sync::ProtocolErrorInfo::Action::MigrateToFLX ||
617+
m_client_reset_error->server_requests_action == sync::ProtocolErrorInfo::Action::RevertToPBS;
610618
become_inactive(std::move(lock), Status::OK(), cancel_subscription_notifications); // unlocks the lock
611619

612620
// Once the session is inactive, update sync config and subscription store after migration.
613-
if (server_requests_action == sync::ProtocolErrorInfo::Action::MigrateToFLX ||
614-
server_requests_action == sync::ProtocolErrorInfo::Action::RevertToPBS) {
621+
if (is_migration) {
615622
apply_sync_config_after_migration_or_rollback();
616623
auto flx_sync_requested = config(&SyncConfig::flx_sync_requested);
617624
update_subscription_store(flx_sync_requested, std::move(new_subs));
@@ -695,7 +702,7 @@ void SyncSession::handle_error(sync::SessionErrorInfo error)
695702
case ClientResyncMode::RecoverOrDiscard:
696703
[[fallthrough]];
697704
case ClientResyncMode::Recover:
698-
download_fresh_realm(error.server_requests_action);
705+
download_fresh_realm(error);
699706
return; // do not propagate the error to the user at this point
700707
}
701708
break;
@@ -707,7 +714,7 @@ void SyncSession::handle_error(sync::SessionErrorInfo error)
707714
m_migration_store->migrate_to_flx(*error.migration_query_string,
708715
m_original_sync_config->partition_value);
709716
save_sync_config_after_migration_or_rollback();
710-
download_fresh_realm(error.server_requests_action);
717+
download_fresh_realm(error);
711718
return;
712719
case sync::ProtocolErrorInfo::Action::RevertToPBS:
713720
// If the client was updated to use FLX natively, but the server was rolled back to PBS,
@@ -721,7 +728,7 @@ void SyncSession::handle_error(sync::SessionErrorInfo error)
721728
// Original config was PBS, rollback the migration
722729
m_migration_store->rollback_to_pbs();
723730
save_sync_config_after_migration_or_rollback();
724-
download_fresh_realm(error.server_requests_action);
731+
download_fresh_realm(error);
725732
return;
726733
case sync::ProtocolErrorInfo::Action::RefreshUser:
727734
if (auto u = user()) {
@@ -824,17 +831,15 @@ void SyncSession::handle_progress_update(uint64_t downloaded, uint64_t downloada
824831
upload_estimate, query_version);
825832
}
826833

827-
static sync::Session::Config::ClientReset make_client_reset_config(const RealmConfig& base_config,
828-
const std::shared_ptr<SyncConfig>& sync_config,
829-
DBRef&& fresh_copy, bool recovery_is_allowed,
830-
bool schema_migration_detected)
834+
835+
static sync::Session::Config::ClientReset
836+
make_client_reset_config(const RealmConfig& base_config, const std::shared_ptr<SyncConfig>& sync_config,
837+
DBRef&& fresh_copy, sync::SessionErrorInfo&& error_info, bool schema_migration_detected)
831838
{
832839
REALM_ASSERT(sync_config->client_resync_mode != ClientResyncMode::Manual);
833840

834-
sync::Session::Config::ClientReset config;
835-
config.mode = sync_config->client_resync_mode;
836-
config.fresh_copy = std::move(fresh_copy);
837-
config.recovery_is_allowed = recovery_is_allowed;
841+
sync::Session::Config::ClientReset config{sync_config->client_resync_mode, std::move(fresh_copy),
842+
std::move(error_info.status), error_info.server_requests_action};
838843

839844
// The conditions here are asymmetric because if we have *either* a before
840845
// or after callback we need to make sure to initialize the local schema
@@ -940,17 +945,15 @@ void SyncSession::create_sync_session()
940945
}
941946
session_config.custom_http_headers = sync_config.custom_http_headers;
942947

943-
if (m_server_requests_action != sync::ProtocolErrorInfo::Action::NoAction) {
944-
// Migrations are allowed to recover local data.
945-
const bool allowed_to_recover = m_server_requests_action == sync::ProtocolErrorInfo::Action::ClientReset ||
946-
m_server_requests_action == sync::ProtocolErrorInfo::Action::MigrateToFLX ||
947-
m_server_requests_action == sync::ProtocolErrorInfo::Action::RevertToPBS;
948-
// Use the original sync config, not the updated one from the migration store
949-
session_config.client_reset_config =
950-
make_client_reset_config(m_config, m_original_sync_config, std::move(m_client_reset_fresh_copy),
951-
allowed_to_recover, m_previous_schema_version.has_value());
952-
session_config.schema_version = m_previous_schema_version.value_or(m_config.schema_version);
953-
m_server_requests_action = sync::ProtocolErrorInfo::Action::NoAction;
948+
if (m_client_reset_error) {
949+
auto client_reset_error = std::exchange(m_client_reset_error, std::nullopt);
950+
if (client_reset_error->server_requests_action != sync::ProtocolErrorInfo::Action::NoAction) {
951+
// Use the original sync config, not the updated one from the migration store
952+
session_config.client_reset_config =
953+
make_client_reset_config(m_config, m_original_sync_config, std::move(m_client_reset_fresh_copy),
954+
std::move(*client_reset_error), m_previous_schema_version.has_value());
955+
session_config.schema_version = m_previous_schema_version.value_or(m_config.schema_version);
956+
}
954957
}
955958

956959
session_config.progress_handler = [weak_self](uint_fast64_t downloaded, uint_fast64_t downloadable,

src/realm/object-store/sync/sync_session.hpp

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <realm/object-store/feature_checks.hpp>
2323
#include <realm/object-store/shared_realm.hpp>
2424
#include <realm/object-store/sync/generic_network_transport.hpp>
25+
#include <realm/sync/client_base.hpp>
2526
#include <realm/sync/config.hpp>
2627
#include <realm/sync/subscriptions.hpp>
2728

@@ -40,7 +41,6 @@ class SyncUser;
4041

4142
namespace sync {
4243
class Session;
43-
struct SessionErrorInfo;
4444
class MigrationStore;
4545
} // namespace sync
4646

@@ -406,10 +406,9 @@ class SyncSession : public std::enable_shared_from_this<SyncSession> {
406406
void apply_sync_config_after_migration_or_rollback() REQUIRES(!m_config_mutex, !m_state_mutex);
407407
void save_sync_config_after_migration_or_rollback() REQUIRES(!m_config_mutex);
408408

409-
void download_fresh_realm(sync::ProtocolErrorInfo::Action server_requests_action)
409+
void download_fresh_realm(const sync::SessionErrorInfo& error_info)
410410
REQUIRES(!m_config_mutex, !m_state_mutex, !m_connection_state_mutex);
411-
void handle_fresh_realm_downloaded(DBRef db, Status status,
412-
sync::ProtocolErrorInfo::Action server_requests_action,
411+
void handle_fresh_realm_downloaded(DBRef db, StatusWith<sync::SessionErrorInfo> error_info,
413412
std::optional<sync::SubscriptionSet> new_subs = std::nullopt)
414413
REQUIRES(!m_state_mutex, !m_config_mutex, !m_connection_state_mutex);
415414
void handle_error(sync::SessionErrorInfo) REQUIRES(!m_state_mutex, !m_config_mutex, !m_connection_state_mutex);
@@ -504,8 +503,7 @@ class SyncSession : public std::enable_shared_from_this<SyncSession> {
504503
std::shared_ptr<SyncConfig> m_migrated_sync_config GUARDED_BY(m_config_mutex);
505504
const std::shared_ptr<sync::MigrationStore> m_migration_store;
506505
std::optional<int64_t> m_migration_sentinel_query_version GUARDED_BY(m_state_mutex);
507-
sync::ProtocolErrorInfo::Action
508-
m_server_requests_action GUARDED_BY(m_state_mutex) = sync::ProtocolErrorInfo::Action::NoAction;
506+
std::optional<sync::SessionErrorInfo> m_client_reset_error GUARDED_BY(m_state_mutex);
509507
DBRef m_client_reset_fresh_copy GUARDED_BY(m_state_mutex);
510508
_impl::SyncClient& m_client;
511509
SyncManager* m_sync_manager GUARDED_BY(m_state_mutex) = nullptr;

src/realm/sync/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ set(SYNC_SOURCES
99
noinst/compact_changesets.cpp
1010
noinst/migration_store.cpp
1111
noinst/pending_bootstrap_store.cpp
12+
noinst/pending_reset_store.cpp
1213
noinst/protocol_codec.cpp
1314
noinst/sync_metadata_schema.cpp
1415
noinst/sync_schema_migration.cpp

0 commit comments

Comments
 (0)