8000 Client reset w/recovery can throw a MultipleSyncAgents if interrupted during the fresh realm download by jbreams · Pull Request #6218 · realm/realm-core · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Client reset w/recovery can throw a MultipleSyncAgents if interrupted during the fresh realm download #6218

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* Fixed diverging history in flexible sync if writes occur during bootstrap to objects that just came into view ([#5804](https://github.com/realm/realm-core/issues/5804), since v11.7.0)
* Fix several data races when opening cached frozen Realms. New frozen Realms were added to the cache and the lock released before they were fully initialized, resulting in races if they were immediately read from the cache on another thread ([PR #6211](https://github.com/realm/realm-core/pull/6211), since v6.0.0).
* Properties and types not present in the requested schema would be missing from the reported schema in several scenarios, such as if the Realm was being opened with a different schema version than the persisted one, and if the new tables or columns were added while the Realm instance did not have an active read transaction ([PR #6211](https://github.com/realm/realm-core/pull/6211), since v13.2.0).
* If a client reset w/recovery or discard local is interrupted while the "fresh" realm is being downloaded, the sync client may crash with a MultpleSyncAgents exception ([#6217](https://github.com/realm/realm-core/issues/6217), since v11.13.0)

### Breaking changes
* `SyncSession::log_out()` has been renamed to `SyncSession::force_close()` to reflect what it actually does ([#6183](https://github.com/realm/realm-core/pull/6183))
Expand Down
54 changes: 32 additions & 22 deletions src/realm/object-store/sync/sync_session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,9 @@ void SyncSession::download_fresh_realm(sync::ProtocolErrorInfo::Action server_re
auto mode = config(&SyncConfig::client_resync_mode);
if (mode == ClientResyncMode::Recover) {
handle_fresh_realm_downloaded(
nullptr, {"A client reset is required but the server does not permit recovery for this client"},
nullptr,
{ErrorCodes::RuntimeError,
"A client reset is required but the server does not permit recovery for this client"},
server_requests_action);
}
}
Expand Down Expand Up @@ -419,36 +421,36 @@ void SyncSession::download_fresh_realm(sync::ProtocolErrorInfo::Action server_re
db = DB::create(sync::make_client_replication(), fresh_path, options);
}
}
catch (std::exception const& e) {
catch (...) {
// Failed to open the fresh path after attempting to delete it, so we
// just can't do automatic recovery.
handle_fresh_realm_downloaded(nullptr, std::string(e.what()), server_requests_action);
handle_fresh_realm_downloaded(nullptr, exception_to_status(), server_requests_action);
return;
}

util::CheckedLockGuard state_lock(m_state_mutex);
if (m_state != State::Active) {
return;
}
std::shared_ptr<SyncSession> sync_session;
std::shared_ptr<SyncSession> fresh_sync_session;
{
util::CheckedLockGuard config_lock(m_config_mutex);
RealmConfig config = m_config;
config.path = fresh_path;
// deep copy the sync config so we don't modify the live session's config
config.sync_config = std::make_shared<SyncConfig>(*m_config.sync_config);
config.sync_config->stop_policy = SyncSessionStopPolicy::Immediately;
config.sync_config->client_resync_mode = ClientResyncMode::Manual;
sync_session = create(m_client, db, config, m_sync_manager);
fresh_sync_session = m_sync_manager->get_session(db, config);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really the line that fixes the issue. If you undid this change, the test should start failing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's what that failure looks like:

Filters: sync: large reset with recovery is restartable
Randomness seeded to: 211705501
Realm sync client ([realm-core-13.2.0])
Supported protocol versions: 2-7
Platform: macOS Darwin 21.6.0 Darwin Kernel Version 21.6.0: Sun Nov  6 23:31:13 PST 2022; root:xnu-8020.240.14~1/RELEASE_ARM64_T6000 arm64
Build mode: Debug
Config param: one_connection_per_session = true
Config param: connect_timeout = 120000 ms
Config param: connection_linger_time = 30000 ms
Config param: ping_keepalive_period = 60000 ms
Config param: pong_keepalive_timeout = 120000 ms
Config param: fast_reconnect_limit = 60000 ms
Config param: disable_upload_compaction = false
Config param: disable_sync_to_disk = false
User agent string: 'RealmSync/13.2.0 (macOS Darwin 21.6.0 Darwin Kernel Version 21.6.0: Sun Nov  6 23:31:13 PST 2022; root:xnu-8020.240.14~1/RELEASE_ARM64_T6000 arm64)  '
App: register_email: realm_tests_do_autoverifyJpofdLkoGf@nKteedCeiS.com
App: update_hostname: http://localhost:9090 | ws://localhost:9090
App: log_in_with_credentials: app_id: client_reset_tests-ooilt - app_version: A Local App Version
App: version info: platform: Object Store Platform Tests  version: Object Store Platform Version Blah - sdk: An sdk name - sdk version: An sdk version - core version: 13.2.0
App: do_authenticated_request: GET http://localhost:9090/api/client/v2.0/auth/profile
App: register_email: realm_tests_do_autoverifyEtHSkkNfkT@fQnZVIwjQp.com
App: log_in_with_credentials: app_id: client_reset_tests-ooilt - app_version: A Local App Version
App: version info: platform: Object Store Platform Tests  version: Object Store Platform Version Blah - sdk: An sdk name - sdk version: An sdk version - core version: 13.2.0
App: do_authenticated_request: GET http://localhost:9090/api/client/v2.0/auth/profile
Connection[1]: Session[1]: Binding '/var/folders/s3/rtk055q91y15yg1hg2c373w40000gp/T/realm_T9TZDK/realm.h7emc9' to '"yvpGAYSBoSLLzvfvslix"'
Connection[1]: Session[1]: Activating
Connection[1]: Session[1]: client_reset_config = false, Realm exists = true, client reset = false
Connection[1]: Session[1]: client_file_ident = 0, client_file_ident_salt = 0
Connection[1]: Session[1]: last_version_available  = 0
Connection[1]: Session[1]: progress_server_version = 0
Connection[1]: Session[1]: progress_client_version = 0
Connection[1]: Session[1]: Progress handler called, downloaded = 0, downloadable(total) = 0, uploaded = 0, uploadable = 0, reliable_download_progress = false, snapshot version = 1
WebSocket::Websocket()
Resolving 'localhost:9090'
Connection[1]: Session[1]: Progress handler called, downloaded = 0, downloadable(total) = 0, uploaded = 0, uploadable = 55, reliable_download_progress = false, snapshot version = 2
Connection[1]: Session[1]: Progress handler called, downloaded = 0, downloadable(total) = 0, uploaded = 0, uploadable = 55, reliable_download_progress = false, snapshot version = 2
Connection[1]: Session[1]: Progress handler called, downloaded = 0, downloadable(total) = 0, uploaded = 0, uploadable = 169, reliable_download_progress = false, snapshot version = 3
Connection[1]: Session[1]: Progress handler called, downloaded = 0, downloadable(total) = 0, uploaded = 0, uploadable = 169, reliable_download_progress = false, snapshot version = 3
Connecting to endpoint '::1:9090' (1/2)
Connected to endpoint '::1:9090' (from '::1:62979')
WebSocket::initiate_client_handshake()
WebSocket::handle_http_response_received()
Connection[1]: Negotiated protocol version: 7
Connection[1]: Connected to app services with request id: "63c9f1862533837b7dd53b83"
Connection[1]: Will emit a ping in 48725 milliseconds
Connection[1]: Session[1]: Received: IDENT(client_file_ident=1, client_file_ident_salt=2976371793880892543)
Connection[1]: Session[1]: Sending: IDENT(client_file_ident=1, client_file_ident_salt=2976371793880892543, scan_server_version=0, scan_client_version=0, latest_server_version=0, latest_server_version_salt=0)
Connection[1]: Session[1]: Sending: MARK(request_ident=1)
Connection[1]: Received: DOWNLOAD CHANGESET(server_version=1, client_version=0, origin_timestamp=254108550970, origin_file_ident=2, original_changeset_size=52, changeset_size=52)
Connection[1]: Session[1]: Received: DOWNLOAD(download_server_version=1, download_client_version=0, latest_server_version=1, latest_server_version_salt=2053986377959573883, upload_client_version=0, upload_server_version=0, downloadable_bytes=0, last_in_batch=true, query_version=0, num_changesets=1, ...)
Connection[1]: Session[1]: Finished changeset indexing (incoming: 1 changeset(s) / 3 instructions, local: 2 changeset(s) / 6 instructions, conflict group(s): 2)
Connection[1]: Session[1]: Finished transforming 2 local changesets through 1 incoming changesets (6 vs 3 instructions, in 2 conflict groups)
Connection[1]: Session[1]: Integrated 1 changesets out of 1
Connection[1]: Session[1]: 1 remote changeset integrated, producing client version 5
Connection[1]: Session[1]: Progress handler called, downloaded = 52, downloadable(total) = 52, uploaded = 0, uploadable = 169, reliable_download_progress = true, snapshot version = 5
Connection[1]: Session[1]: Progress handler called, downloaded = 52, downloadable(total) = 52, uploaded = 0, uploadable = 169, reliable_download_progress = true, snapshot version = 5
Connection[1]: Session[1]: Received: MARK(request_ident=1)
Connection[1]: Session[1]: Sending: UPLOAD(progress_client_version=5, progress_server_version=1, locked_server_version=1, num_changesets=2)
Connection[1]: Session[1]: Fetching changeset for upload (client_version=2, server_version=0, changeset_size=55, origin_timestamp=254108550891, origin_file_ident=0)
Connection[1]: Session[1]: Fetching changeset for upload (client_version=3, server_version=0, changeset_size=114, origin_timestamp=254108550892, origin_file_ident=0)
Connection[1]: Session[1]: Received: DOWNLOAD(download_server_version=3, download_client_version=3, latest_server_version=3, latest_server_version_salt=6433580853625984864, upload_client_version=5, upload_server_version=1, downloadable_bytes=0, last_in_batch=true, query_version=0, num_changesets=0, ...)
Connection[1]: Session[1]: Sending: UPLOAD(progress_client_version=6, progress_server_version=3, locked_server_version=3, num_changesets=0)
Connection[1]: Session[1]: Progress handler called, downloaded = 52, downloadable(total) = 52, uploaded = 169, uploadable = 169, reliable_download_progress = true, snapshot version = 6
App: call_function: count service_name: BackingDB args_bson: [{"database":"test_data_client_reset_tests_63c9f186e67fa430b3cf9d45","collection":"object","query":{"_id":{"$oid":"63c9f186e67fa430b3cf9d46"}}}]
App: do_authenticated_request: POST http://localhost:9090/api/client/v2.0/app/client_reset_tests-ooilt/functions/call
App: call_function: count service_name: BackingDB - results: {"$numberLong":"0"}
App: call_function: count service_name: BackingDB args_bson: [{"database":"test_data_client_reset_tests_63c9f186e67fa430b3cf9d45","collection":"object","query":{"_id":{"$oid":"63c9f186e67fa430b3cf9d46"}}}]
App: do_authenticated_request: POST http://localhost:9090/api/client/v2.0/app/client_reset_tests-ooilt/functions/call
App: call_function: count service_name: BackingDB - results: {"$numberLong":"0"}
App: call_function: count service_name: BackingDB args_bson: [{"database":"test_data_client_reset_tests_63c9f186e67fa430b3cf9d45","collection":"object","query":{"_id":{"$oid":"63c9f186e67fa430b3cf9d46"}}}]
App: do_authenticated_request: POST http://localhost:9090/api/client/v2.0/app/client_reset_tests-ooilt/functions/call
App: call_function: count service_name: BackingDB - results: {"$numberLong":"1"}
Connection[1]: Session[1]: Initiating deactivation
Connection[1]: Session[1]: Sending: UNBIND
Connection[1]: Session[1]: Deactivation completed
Connection[1]: Disconnected
Connection[1]: Destroying connection object
Connection[2]: Session[2]: Binding '/var/folders/s3/rtk055q91y15yg1hg2c373w40000gp/T/realm_sYOG9i/realm.RUdojY' to '"yvpGAYSBoSLLzvfvslix"'
Connection[2]: Session[2]: Activating
Connection[2]: Session[2]: client_reset_config = false, Realm exists = true, client reset = false
Connection[2]: Session[2]: client_file_ident = 0, client_file_ident_salt = 0
Connection[2]: Session[2]: last_version_available  = 0
Connection[2]: Session[2]: progress_server_version = 0
Connection[2]: Session[2]: progress_client_version = 0
Connection[2]: Session[2]: Progress handler called, downloaded = 0, downloadable(total) = 0, uploaded = 0, uploadable =
8000
 0, reliable_download_progress = false, snapshot version = 1
WebSocket::Websocket()
Resolving 'localhost:9090'
Connection[2]: Session[2]: Progress handler called, downloaded = 0, downloadable(total) = 0, uploaded = 0, uploadable = 55, reliable_download_progress = false, snapshot version = 2
Connection[2]: Session[2]: Progress handler called, downloaded = 0, downloadable(total) = 0, uploaded = 0, uploadable = 55, reliable_download_progress = false, snapshot version = 2
Connecting to endpoint '::1:9090' (1/2)
Connected to endpoint '::1:9090' (from '::1:64000')
WebSocket::initiate_client_handshake()
WebSocket::handle_http_response_received()
Connection[2]: Negotiated protocol version: 7
Connection[2]: Connected to app services with request id: "63c9f18c2533837b7dd54470"
Connection[2]: Will emit a ping in 38666 milliseconds
Connection[2]: Session[2]: Received: IDENT(client_file_ident=3, client_file_ident_salt=6395488297736614125)
Connection[2]: Session[2]: Sending: IDENT(client_file_ident=3, client_file_ident_salt=6395488297736614125, scan_server_version=0, scan_client_version=0, latest_server_version=0, latest_server_version_salt=0)
Connection[2]: Session[2]: Progress handler called, downloaded = 0, downloadable(total) = 0, uploaded = 0, uploadable = 13114983, reliable_download_progress = false, snapshot version = 4
Connection[2]: Session[2]: Progress handler called, downloaded = 0, downloadable(total) = 0, uploaded = 0, uploadable = 13114983, reliable_download_progress = false, snapshot version = 4
Connection[2]: Session[2]: Sending: MARK(request_ident=1)
Connection[2]: Received: DOWNLOAD CHANGESET(server_version=1, client_version=0, origin_timestamp=254108556667, origin_file_ident=2, original_changeset_size=52, changeset_size=52)
Connection[2]: Received: DOWNLOAD CHANGESET(server_version=2, client_version=0, origin_timestamp=254108556651, origin_file_ident=1, original_changeset_size=129, changeset_size=129)
Connection[2]: Session[2]: Received: DOWNLOAD(download_server_version=2, download_client_version=0, latest_server_version=2, latest_server_version_salt=1393523807200592440, upload_client_version=0, upload_server_version=0, downloadable_bytes=0, last_in_batch=true, query_version=0, num_changesets=2, ...)
Connection[2]: Session[2]: Finished changeset indexing (incoming: 2 changeset(s) / 7 instructions, local: 2 changeset(s) / 303 instructions, conflict group(s): 102)
Connection[2]: Session[2]: Finished transforming 2 local changesets through 2 incoming changesets (303 vs 7 instructions, in 102 conflict groups)
Connection[2]: Session[2]: Integrated 2 changesets out of 2
Connection[2]: Session[2]: 2 remote changesets integrated, producing client version 5
Connection[2]: Session[2]: Progress handler called, downloaded = 181, downloadable(total) = 181, uploaded = 0, uploadable = 13114983, reliable_download_progress = true, snapshot version = 5
Connection[2]: Session[2]: Progress handler called, downloaded = 181, downloadable(total) = 181, uploaded = 0, uploadable = 13114983, reliable_download_progress = true, snapshot version = 5
Connection[2]: Session[2]: Received: MARK(request_ident=1)
Connection[2]: Session[2]: Sending: UPLOAD(progress_client_version=3, progress_server_version=0, locked_server_version=2, num_changesets=2)
Connection[2]: Session[2]: Fetching changeset for upload (client_version=2, server_version=0, changeset_size=55, origin_timestamp=254108556669, origin_file_ident=0)
Connection[2]: Session[2]: Fetching changeset for upload (client_version=3, server_version=0, changeset_size=13114928, origin_timestamp=254108557404, origin_file_ident=0)
Connection[2]: Session[2]: Sending: UPLOAD(progress_client_version=5, progress_server_version=2, locked_server_version=2, num_changesets=0)
Connection[2]: Session[2]: Received: DOWNLOAD(download_server_version=4, download_client_version=3, latest_server_version=4, latest_server_version_salt=4590382444970176979, upload_client_version=3, upload_server_version=0, downloadable_bytes=0, last_in_batch=true, query_version=0, num_changesets=0, ...)
Connection[2]: Session[2]: Sending: UPLOAD(progress_client_version=6, progress_server_version=4, locked_server_version=4, num_changesets=0)
Connection[2]: Session[2]: Progress handler called, downloaded = 181, downloadable(total) = 181, uploaded = 13114983, uploadable = 13114983, reliable_download_progress = true, snapshot version = 6
Connection[2]: Session[2]: Initiating deactivation
Connection[2]: Session[2]: Sending: UNBIND
Connection[2]: Session[2]: Deactivation completed
Connection[2]: Disconnected
Connection[2]: Destroying connection object
Connection[3]: Session[3]: Binding '/var/folders/s3/rtk055q91y15yg1hg2c373w40000gp/T/realm_T9TZDK/realm.h7emc9' to '"yvpGAYSBoSLLzvfvslix"'
Connection[3]: Session[3]: Activating
Connection[3]: Session[3]: client_reset_config = false, Realm exists = true, client reset = false
Connection[3]: Session[3]: client_file_ident = 1, client_file_ident_salt = 2976371793880892543
Connection[3]: Session[3]: last_version_available  = 6
Connection[3]: Session[3]: progress_server_version = 3
Connection[3]: Session[3]: progress_client_version = 3
Connection[3]: Session[3]: Progress handler called, downloaded = 52, downloadable(total) = 52, uploaded = 169, uploadable = 169, reliable_download_progress = false, snapshot version = 6
WebSocket::Websocket()
Resolving 'localhost:9090'
Connecting to endpoint '::1:9090' (1/2)
Connected to endpoint '::1:9090' (from '::1:64002')
WebSocket::initiate_client_handshake()
WebSocket::handle_http_response_received()
Connection[3]: Negotiated protocol version: 7
Connection[3]: Connected to app services with request id: "63c9f18e2533837b7dd544c2"
Connection[3]: Will emit a ping in 34599 milliseconds
Connection[3]: Session[3]: Sending: IDENT(client_file_ident=1, client_file_ident_salt=2976371793880892543, scan_server_version=3, scan_client_version=3, latest_server_version=3, latest_server_version_salt=6433580853625984864)
Connection[3]: Session[3]: Sending: MARK(request_ident=1)
Connection[3]: Session[3]: Received: ERROR "Diverging histories (IDENT)" (error_code=211, try_again=false, error_action=ClientReset)
Connection[3]: Session[3]: Suspended
Connection[3]: Session[3]: Sending: UNBIND
Connection[3]: Session[3]: Initiating deactivation
Connection[4]: Session[4]: Binding '/var/folders/s3/rtk055q91y15yg1hg2c373w40000gp/T/realm_T9TZDK/realm.h7emc9' to '"yvpGAYSBoSLLzvfvslix"'
Connection[4]: Session[4]: Activating
Connection[4]: Session[4]: client_reset_config = false, Realm exists = true, client reset = false
Connection[4]: Session[4]: client_file_ident = 1, client_file_ident_salt = 2976371793880892543
Connection[4]: Session[4]: last_version_available  = 6
Connection[4]: Session[4]: progress_server_version = 3
Connection[4]: Session[4]: progress_client_version = 3
Connection[4]: Session[4]: Progress handler called, downloaded = 52, downloadable(total) = 52, uploaded = 169, uploadable = 169, reliable_download_progress = false, snapshot version = 6
Connection[5]: Session[5]: Binding '/var/folders/s3/rtk055q91y15yg1hg2c373w40000gp/T/realm_T9TZDK/realm.h7emc9.fresh' to '"yvpGAYSBoSLLzvfvslix"'
Connection[5]: Session[5]: Activating
Connection[5]: Session[5]: client_reset_config = false, Realm exists = true, client reset = false
Connection[5]: Session[5]: client_file_ident = 0, client_file_ident_salt = 0
Connection[5]: Session[5]: last_version_available  = 0
Connection[5]: Session[5]: progress_server_version = 0
Connection[5]: Session[5]: progress_client_version = 0
Connection[5]: Session[5]: Progress handler called, downloaded = 0, downloadable(total) = 0, uploaded = 0, uploadable = 0, reliable_download_progress = false, snapshot version = 1
Connection[3]: Session[3]: Deactivation completed
Connection[3]: Allowing reconnection in 958 milliseconds
Connection[3]: Disconnected
WebSocket::Websocket()
Resolving 'localhost:9090'
WebSocket::Websocket()
Resolving 'localhost:9090'
Connection[3]: Destroying connection object
Connecting to endpoint '::1:9090' (1/2)
Connected to endpoint '::1:9090' (from '::1:64003')
WebSocket::initiate_client_handshake()
Connecting to endpoint '::1:9090' (1/2)
Connected to endpoint '::1:9090' (from '::1:64004')
WebSocket::initiate_client_handshake()
WebSocket::handle_http_response_received()
Connection[5]: Negotiated protocol version: 7
Connection[5]: Connected to app services with request id: "63c9f18e2533837b7dd544dd"
Connection[5]: Will emit a ping in 32154 milliseconds
WebSocket::handle_http_response_received()
Connection[4]: Negotiated protocol version: 7
Connection[4]: Connected to app services with request id: "63c9f18e2533837b7dd544dc"
Connection[4]: Will emit a ping in 29034 milliseconds
Connection[4]: Session[4]: Sending: IDENT(client_file_ident=1, client_file_ident_salt=2976371793880892543, scan_server_version=3, scan_client_version=3, latest_server_version=3, latest_server_version_salt=6433580853625984864)
Connection[4]: Session[4]: Sending: MARK(request_ident=1)
Connection[4]: Session[4]: Received: ERROR "Diverging histories (IDENT)" (error_code=211, try_again=false, error_action=ClientReset)
Connection[4]: Session[4]: Suspended
Connection[4]: Session[4]: Sending: UNBIND
libc++abi: terminating with uncaught exception of type realm::MultipleSyncAgents: Multiple sync agents attempted to join the same session
Exception backtrace:
0   realm-object-store-tests            0x00000001040f88d8 _ZN5realm4util6detail26ExceptionWithBacktraceBaseC2Ev + 48
1   realm-object-store-tests            0x00000001040f883c _ZN5realm4util22ExceptionWithBacktraceISt9exceptionEC2IJEEEDpOT_ + 64
2   realm-object-store-tests            0x0000000106bfbf94 _ZN5realm18MultipleSyncAgentsC2Ev + 52
3   realm-object-store-tests            0x0000000106bec6dc _ZN5realm18MultipleSyncAgentsC1Ev + 28
4   realm-object-store-tests            0x0000000106bec634 _ZN5realm2DB16claim_sync_agentEv + 164
5   realm-object-store-tests            0x00000001068c2704 _ZN5realm4sync14SessionWrapper9actualizeENSt3__15tupleIJNS0_16ProtocolEnvelopeENS2_12basic_stringIcNS2_11char_traitsIcEENS2_9allocatorIcEEEEtEEE + 188
6   realm-object-store-tests            0x00000001068c20d0 _ZN5realm4sync10ClientImpl39actualize_and_finalize_session_wrappersEv + 416
7   realm-object-store-tests            0x0000000106825538 _ZZN5realm4sync10ClientImplC1ENS0_12ClientConfigEENK3$_0clENS_6StatusE + 260
8   realm-object-store-tests            0x00000001068253fc _ZN5realm4util14UniqueFunctionIFvNS_6StatusEEE17call_regular_voidIZNS_4sync10ClientImplC1ENS6_12ClientConfigEE3$_0EEvNSt3__117integral_constantIbLb1EEERT_OS2_ + 64
9   realm-object-store-tests            0x0000000106825384 _ZN5realm4util14UniqueFunctionIFvNS_6StatusEEE12SpecificImplIZNS_4sync10ClientImplC1ENS6_12ClientConfigEE3$_0E4callEOS2_ + 52
10  realm-object-store-tests            0x00000001068d9c44 _ZNK5realm4util14UniqueFunctionIFvNS_6StatusEEEclES2_ + 128
11  realm-object-store-tests            0x00000001068d9b88 _ZZN5realm4sync7TriggerINS0_18SyncSocketProviderEE7triggerEvENKUlNS_6StatusEE_clES4_ + 172
12  realm-object-store-tests            0x00000001068d9a90 _ZN5realm4util14UniqueFunctionIFvNS_6StatusEEE17call_regular_voidIZNS_4sync7TriggerINS6_18SyncSocketProviderEE7triggerEvEUlS2_E_EEvNSt3__117integral_constantIbLb1EEERT_OS2_ + 64
13  realm-object-store-tests            0x00000001068d9900 _ZN5realm4util14UniqueFunctionIFvNS_6StatusEEE12SpecificImplIZNS_4sync7TriggerINS6_18SyncSocketProviderEE7triggerEvEUlS2_E_E4callEOS2_ + 52
14  realm-object-store-tests            0x00000001068d9c44 _ZNK5realm4util14UniqueFunctionIFvNS_6StatusEEEclES2_ + 128
15  realm-object-store-tests            0x00000001069d6934 _ZN5realm4sync7network7Service8PostOperINS_4util14UniqueFunctionIFvNS_6StatusEEEEE19recycle_and_executeEv + 92
16  realm-object-store-tests            0x00000001069fc6a4 _ZN5realm4sync7network7Service4Impl7executeERNSt3__110unique_ptrINS2_9AsyncOperENS2_18LendersOperDeleterEEE + 36
17  realm-object-store-tests            0x00000001069f7b58 _ZN5realm4sync7network7Service4Impl3runEv + 296
18  realm-object-store-tests            0x00000001069f79fc _ZN5realm4sync7network7Service3runEv + 28
19  realm-object-store-tests            0x00000001069d6354 _ZN5realm4sync9websocket21DefaultSocketProvider3runEv + 32
20  realm-object-store-tests            0x00000001068c1628 _ZN5realm4sync10ClientImpl3runEv + 76
21  realm-object-store-tests            0x00000001068ca920 _ZN5realm4sync6Client3runEv + 28
22  realm-object-store-tests            0x000000010646f18c _ZZN5realm5_impl10SyncClientC1ERKNSt3__110shared_ptrINS_4util6LoggerEEERKNS_16SyncClientConfigENS2_8weak_ptrIKNS_11SyncManagerEEEENKUlvE0_clEv + 256
23  realm-object-store-tests            0x000000010646f044 _ZNSt3__1L8__invokeIZN5realm5_impl10SyncClientC1ERKNS_10shared_ptrINS1_4util6LoggerEEERKNS1_16SyncClientConfigENS_8weak_ptrIKNS1_11SyncManagerEEEEUlvE0_JEEEDTclscT_fp_spscT0_fp0_EEOSI_DpOSJ_ + 24
24  realm-object-store-tests            0x000000010646efe0 _ZNSt3__1L16__thread_executeINS_10unique_ptrINS_15__thread_structENS_14default_deleteIS2_EEEEZN5realm5_impl10SyncClientC1ERKNS_10shared_ptrINS6_4util6LoggerEEERKNS6_16SyncClientConfigENS_8weak_ptrIKNS6_11SyncManagerEEEEUlvE0_JEJEEEvRNS_5tupleIJT_T0_DpT1_EEENS_15__tuple_indicesIJXspT2_EEEE + 32
25  realm-object-store-tests            0x000000010646e804 _ZNSt3__1L14__thread_proxyINS_5tupleIJNS_10unique_ptrINS_15__thread_structENS_14default_deleteIS3_EEEEZN5realm5_impl10SyncClientC1ERKNS_10shared_ptrINS7_4util6LoggerEEERKNS7_16SyncClientConfigENS_8weak_ptrIKNS7_11SyncManagerEEEEUlvE0_EEEEEPvSP_ + 84
26  libsystem_pthread.dylib             0x00000001b12ac26c _pthread_start + 148
27  libsystem_pthread.dylib             0x00000001b12a708c thread_start + 8

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
realm-object-store-tests is a Catch2 v3.0.1 host application.
Run with -? for options

-------------------------------------------------------------------------------
sync: large reset with recovery is restartable
-------------------------------------------------------------------------------
/Users/jreams/Documents/git/realm-core/test/object-store/sync/client_reset.cpp:111
...............................................................................

/Users/jreams/Documents/git/realm-core/test/object-store/sync/client_reset.cpp:111: FAILED:
  {Unknown expression after the reported line}
due to a fatal error condition:
  SIGABRT - Abort (abnormal termination) signal

===============================================================================
test cases:  1 |  0 passed | 1 failed
assertions: 15 | 14 passed | 1 failed

auto& history = static_cast<sync::ClientReplication&>(*db->get_replication());
// the fresh Realm may apply writes to this db after it has outlived its sync session
// the writes are used to generate a changeset for recovery, but are never committed
history.set_write_validator_factory({});
}

sync_session->assert_mutex_unlocked();
fresh_sync_session->assert_mutex_unlocked();
if (m_flx_subscription_store) {
sync::SubscriptionSet active = m_flx_subscription_store->get_active();
auto fresh_sub_store = sync_session->get_flx_subscription_store();
auto fresh_sub_store = fresh_sync_session->get_flx_subscription_store();
REALM_ASSERT(fresh_sub_store);
auto fresh_mut_sub = fresh_sub_store->get_latest().make_mutable_copy();
fresh_mut_sub.import(active);
Expand All @@ -458,37 +460,41 @@ void SyncSession::download_fresh_realm(sync::ProtocolErrorInfo::Action server_re
.get_async([=, weak_self = weak_from_this()](StatusWith<sync::SubscriptionSet::State> s) {
// Keep the sync session alive while it's downloading, but then close
// it immediately
sync_session->close();
fresh_sync_session->force_close();
if (auto strong_self = weak_self.lock()) {
if (s.is_ok()) {
strong_self->handle_fresh_realm_downloaded(db, none, server_requests_action);
strong_self->handle_fresh_realm_downloaded(db, Status::OK(), server_requests_action);
}
else {
strong_self->handle_fresh_realm_downloaded(nullptr, s.get_status().reason(),
server_requests_action);
strong_self->handle_fresh_realm_downloaded(nullptr, s.get_status(), server_requests_action);
}
}
});
}
else { // pbs
sync_session->wait_for_download_completion([=, weak_self = weak_from_this()](std::error_code ec) {
fresh_sync_session->wait_for_download_completion([=, weak_self = weak_from_this()](std::error_code ec) {
// Keep the sync session alive while it's downloading, but then close
// it immediately
sync_session->close();
fresh_sync_session->force_close();
if (auto strong_self = weak_self.lock()) {
if (ec) {
strong_self->handle_fresh_realm_downloaded(nullptr, ec.message(), server_requests_action);
if (ec == util::error::operation_aborted) {
strong_self->handle_fresh_realm_downloaded(nullptr, {ErrorCodes::OperationAborted, ec.message()},
server_requests_action);
}
else if (ec) {
strong_self->handle_fresh_realm_downloaded(nullptr, {ErrorCodes::RuntimeError, ec.message()},
server_requests_action);
}
else {
strong_self->handle_fresh_realm_downloaded(db, none, server_requests_action);
strong_self->handle_fresh_realm_downloaded(db, Status::OK(), server_requests_action);
}
}
});
}
sync_session->revive_if_needed();
fresh_sync_session->revive_if_needed();
}

void SyncSession::handle_fresh_realm_downloaded(DBRef db, util::Optional<std::string> error_message,
void SyncSession::handle_fresh_realm_downloaded(DBRef db, Status status,
sync::ProtocolErrorInfo::Action server_requests_action)
{
util::CheckedUniqueLock lock(m_state_mutex);
Expand All @@ -499,7 +505,10 @@ void SyncSession::handle_fresh_realm_downloaded(DBRef db, util::Optional<std::st
// - unable to write the fresh copy to the file system
// - during download of the fresh copy, the fresh copy itself is reset
// - in FLX mode there was a problem fulfilling the previously active subscription
if (error_message) {
if (!status.is_ok()) {
if (status == ErrorCodes::OperationAborted) {
return;
}
lock.unlock();
if (m_flx_subscription_store) {
// In DiscardLocal mode, only the active subscription set is preserved
Expand All @@ -508,12 +517,13 @@ void SyncSession::handle_fresh_realm_downloaded(DBRef db, util::Optional<std::st
auto mut_sub = m_flx_subscription_store->get_active().make_mutable_copy();
m_flx_subscription_store->supercede_all_except(mut_sub);
mut_sub.update_state(sync::SubscriptionSet::State::Error,
util::make_optional<std::string_view>(*error_message));
util::make_optional<std::string_view>(status.reason()));
std::move(mut_sub).commit();
}
const bool is_fatal = true;
SyncError synthetic(make_error_code(sync::Client::Error::auto_client_reset_failure),
util::format("A fatal error occured during client reset: '%1'", error_message), is_fatal);
util::format("A fatal error occured during client reset: '%1'", status.reason()),
is_fatal);
handle_error(synthetic);
return;
}
Expand Down
2 changes: 1 addition & 1 deletion src/realm/object-store/sync/sync_session.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ class SyncSession : public std::enable_shared_from_this<SyncSession> {

void download_fresh_realm(sync::ProtocolErrorInfo::Action server_requests_action)
REQUIRES(!m_config_mutex, !m_state_mutex, !m_connection_state_mutex);
void handle_fresh_realm_downloaded(DBRef db, util::Optional<std::string> error_message,
void handle_fresh_realm_downloaded(DBRef db, Status status,
sync::ProtocolErrorInfo::Action server_requests_action)
REQUIRES(!m_state_mutex, !m_config_mutex, !m_connection_state_mutex);
void handle_error(SyncError) REQUIRES(!m_state_mutex, !m_config_mutex, !m_connection_state_mutex);
Expand Down
95 changes: 95 additions & 0 deletions test/object-store/sync/client_reset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,101 @@ TableRef get_table(Realm& realm, StringData object_type)
namespace cf = realm::collection_fixtures;
using reset_utils::create_object;

TEST_CASE("sync: large reset with recovery is restartable", "[client reset]") {
const reset_utils::Partition partition{"realm_id", random_string(20)};
Property partition_prop = {partition.property_name, PropertyType::String | PropertyType::Nullable};
Schema schema{
{"object",
{
{"_id", PropertyType::ObjectId, Property::IsPrimary{true}},
{"value", PropertyType::String},
partition_prop,
}},
};

std::string base_url = get_base_url();
REQUIRE(!base_url.empty());
auto server_app_config = minimal_app_config(base_url, "client_reset_tests", schema);
server_app_config.partition_key = partition_prop;
TestAppSession test_app_session(create_app(server_app_config));
auto app = test_app_session.app();

create_user_and_log_in(app);
SyncTestFile realm_config(app->current_user(), partition.value, schema);
realm_config.sync_config->client_resync_mode = ClientResyncMode::Recover;
realm_config.sync_config->error_handler = [&](std::shared_ptr<SyncSession>, SyncError err) {
if (err.error_code == util::make_error_code(util::MiscExtErrors::end_of_input)) {
return;
}

if (err.server_requests_action == sync::ProtocolErrorInfo::Action::Warning ||
err.server_requests_action == sync::ProtocolErrorInfo::Action::Transient) {
return;
}

FAIL(util::format("got error from server: %1: %2", err.error_code.value(), err.message));
};

auto realm = Realm::get_shared_realm(realm_config);
std::vector<ObjectId> expected_obj_ids;
{
auto obj_id = ObjectId::gen();
expected_obj_ids.push_back(obj_id);
realm->begin_transaction();
CppContext c(realm);
Object::create(c, realm, "object",
std::any(AnyDict{{"_id", obj_id},
{"value", std::string{"hello world"}},
{partition.property_name, partition.value}}));
realm->commit_transaction();
wait_for_upload(*realm);
reset_utils::wait_for_object_to_persist_to_atlas(app->current_user(), test_app_session.app_session(),
"object", {{"_id", obj_id}});
realm->sync_session()->pause();
}

reset_utils::trigger_client_reset(test_app_session.app_session());
{
SyncTestFile realm_config(app->current_user(), partition.value, schema);
auto second_realm = Realm::get_shared_realm(realm_config);

second_realm->begin_transaction();
CppContext c(second_realm);
for (size_t i = 0; i < 100; ++i) {
auto obj_id = ObjectId::gen();
expected_obj_ids.push_back(obj_id);
Object::create(c, second_realm, "object",
std::any(AnyDict{{"_id", obj_id},
{"value", random_string(1024 * 128)},
{partition.property_name, partition.value}}));
}
second_realm->commit_transaction();

wait_for_upload(*second_realm);
}

realm->sync_session()->resume();
timed_wait_for([&] {
return util::File::exists(_impl::ClientResetOperation::get_fresh_path_for(realm_config.path));
});
realm->sync_session()->pause();
realm->sync_session()->resume();
wait_for_upload(*realm);
wait_for_download(*realm);

realm->refresh();
auto table = realm->read_group().get_table("class_object");
REQUIRE(table->size() == expected_obj_ids.size());
std::vector<ObjectId> found_object_ids;
for (const auto& obj : *table) {
found_object_ids.push_back(obj.get_primary_key().get_object_id());
}

std::stable_sort(expected_obj_ids.begin(), expected_obj_ids.end());
std::stable_sort(found_object_ids.begin(), found_object_ids.end());
REQUIRE(expected_obj_ids == found_object_ids);
}

TEST_CASE("sync: pending client resets are cleared when downloads are complete", "[client reset]") {
const reset_utils::Partition partition{"realm_id", random_string(20)};
Property partition_prop = {partition.property_name, PropertyType::String | PropertyType::Nullable};
Expand Down
0