Handle Sync Errors - C++ SDK
On this page
Handle Sync Errors
While developing an application that uses Device Sync, you should set an error handler. This error handler detects and can respond to any failed sync-related API calls.
Set an error handler on the sync_config. When an error occurs, the C++ SDK calls the error handler with the sync_error object and the sync_session where the error occurred.
auto appConfig = realm::App::configuration(); appConfig.app_id = APP_ID; auto app = realm::App(appConfig); auto user = app.login(realm::App::credentials::anonymous()).get(); auto dbConfig = user.flexible_sync_configuration(); // Setting an error handler on the sync_config gives you access to // sync_session and sync_error dbConfig.sync_config().set_error_handler( [](const realm::sync_session &session, const realm::internal::bridge::sync_error &error) { std::cerr << "A sync error occurred. Message: " << error.message() << std::endl; }); auto syncRealm = realm::db(dbConfig);
Tip
For a list of common Device Sync errors and how to handle them, refer to Sync Errors in the App Services Device Sync documentation.
Client Reset
When using Device Sync, a client reset is an error recovery task that your client app must perform when the server can no longer sync with the device database. In this case, the device must reset its database to a state that matches the server in order to restore the ability to sync.
When this occurs, the unsyncable database on the device may contain data that has not yet synced to the server. The SDK can attempt to recover or discard that data during the client reset process.
For more information about what might cause a client reset to occur, go to Client Resets in the App Services documentation.
Automatic vs. Manual Client Reset
The SDK provides client reset modes that automatically handle most client reset errors. Automatic client reset modes restore your device's database file to a syncable state without closing the database or missing notifications.
All the client reset modes except manual()
perform an automatic client
reset. The differences between the modes are based on how they handle
changes on the device that have not yet synced to the backend.
Choose recover_unsynced_changes()
to handle most client reset
scenarios automatically. This attempts to recover unsynced changes when a
client reset occurs.
In some cases, you may want or need to set a manual client reset handler. You may want to do this if your app requires specific client reset logic that can't be handled automatically.
Specify a Client Reset Mode
The C++ SDK provides the option to specify a client reset handler in your database configuration. This client reset handler can take a client_reset_mode_base. This struct allows you to specify:
A block to execute before the client reset
A block to execute after the client reset
The mode to use when handling the client reset
auto user = app.login(realm::App::credentials::anonymous()).get(); auto syncConfig = user.flexible_sync_configuration(); // Set the client reset handler with your preferred client reset mode. syncConfig.set_client_reset_handler( realm::client_reset::recover_unsynced_changes(beforeReset, afterReset)); auto syncedRealm = realm::db(syncConfig);
You can use one of the available client reset modes to specify how the SDK should attempt to resolve any unsynced data on the device during a client reset:
recover_unsynced_changes()
recover_or_discard_unsynced_changes()
discard_unsynced_changes()
manual()
You can specify a before and after block to execute during the automatic client reset process. You might use this to perform recovery logic that is important to your application.
/* You can define blocks to call before and after the client reset occur if you need to execute specific logic, such as reporting or debugging. */ auto beforeReset = [&](realm::db before) { /* A block called after a client reset error is detected, but before the client recovery process is executed. You could use this block for any custom logic, reporting, debugging etc. You have access to the database before the client reset occurs in this block. */ }; auto afterReset = [&](realm::db device, realm::db server) { /* A block called after the client recovery process has executed. This block could be used for custom recovery, reporting, debugging etc. You have access to the database that is currently on the device - the one that can no longer sync - and the new database that has been restored from the server. */ };
If your app has specific client recovery needs, you
can specify the manual()
client reset mode and set a manual client
reset handler. You might do this if
you have specific custom logic your app must perform during a client reset,
or if the client recovery rules do not
work for your app.
Handle Schema Changes
Client Recovery is a feature that is enabled by default when you configure Device Sync. When Client Recovery is enabled, the SDK can automatically manage the client reset process in most cases. When you make schema changes:
The client can recover unsynced changes when there are no schema changes, or non-breaking schema changes.
When you make breaking schema changes, the automatic client reset modes fall back to a manual error handler. You can set a manual client reset error handler for this case. Automatic client recovery cannot occur when your app makes breaking schema changes.
For information on breaking vs. non-breaking schema changes, see Breaking vs. Non-Breaking Change Quick Reference.
Recover Unsynced Changes
During a client reset, client applications can attempt to recover data in the synced database on the device that has not yet synced to the backend. To recover unsynced changes, Client Recovery must be enabled in your App Services App, which it is by default.
If you want your app to recover changes that have not yet synced, use one of these client recovery modes:
recover_unsynced_changes()
: The client attempts to recover unsynced changes. Choose this mode when you do not want to fall through to discard unsynced changes.recover_or_discard_unsynced_changes()
: The client first attempts to recover changes that have not yet synced. If the client cannot recover unsynced data, it falls through to discard unsynced changes but continues to automatically perform the client reset. Choose this mode when you want to enable automatic client recovery to fall back to discard unsynced changes.
auto user = app.login(realm::App::credentials::anonymous()).get(); auto syncConfig = user.flexible_sync_configuration(); // Set the client reset handler with your preferred client reset mode. syncConfig.set_client_reset_handler( realm::client_reset::recover_unsynced_changes(beforeReset, afterReset)); auto syncedRealm = realm::db(syncConfig);
There may be times when the client reset operation cannot complete in
recover_unsynced_changes()
mode, like when there are breaking schema
changes or Client Recovery is disabled in
the Device Sync configuration. To handle this case, your app can handle a
client reset error in the Sync error handler. For more information, refer to
the manual client reset mode section
on this page.
Client Recovery Rules
When Client Recovery is enabled, these rules determine how objects are integrated, including how conflicts are resolved when both the backend and the client make changes to the same object:
Objects created locally that were not synced before client reset are synced.
If an object is deleted on the server, but is modified on the recovering client, the delete takes precedence and the client discards the update.
If an object is deleted on the recovering client, but not the server, then the client applies the server's delete instruction.
In the case of conflicting updates to the same field, the client update is applied.
Discard Unsynced Changes
The discard_unsynced_changes()
client reset mode permanently deletes all
unsynced changes on the device since the last successful sync. You might
use this mode when your app requires client recovery logic that is not
consistent with the Device Sync client recovery rules, or when you don't want to recover unsynced data.
Do not use discard unsynced changes mode if your application cannot lose device data that has not yet synced to the backend.
To perform an automatic client reset that discards unsynced changes, use the
discard_unsynced_changes()
client reset mode.
auto user = app.login(realm::App::credentials::anonymous()).get(); auto syncConfig = user.flexible_sync_configuration(); // Set the client reset handler with your preferred client reset mode. syncConfig.set_client_reset_handler( realm::client_reset::discard_unsynced_changes(beforeReset, afterReset)); auto syncedRealm = realm::db(syncConfig);
Note
Discard with Recovery
If you'd like to attempt to recover unsynced changes, but discard
any changes that cannot be recovered, refer to the
recover_or_discard_unsynced_changes()
documentation in the
Recover Unsynced Changes section on this page.
There may be times when the client reset operation cannot complete in
discard_unsynced_changes()
mode, like when there are breaking schema
changes. To handle this case, your app can handle a client reset error in the
Sync error handler. For more information, refer to the
Manual Client Reset Mode section on this page.
Manual Client Reset Mode
When you use manual()
client reset mode, you must implement
a custom client reset handler in the Sync error handler. We recommend using the
automatic client recovery modes
when possible, and only choosing manual()
mode if the automatic recovery
logic is not suitable for your app.
auto user = app.login(realm::App::credentials::anonymous()).get(); auto syncConfig = user.flexible_sync_configuration(); // Set the client reset handler to manual client reset mode. syncConfig.set_client_reset_handler(realm::client_reset::manual()); // Define a Sync error handler for handling the client reset. syncConfig.sync_config().set_error_handler( [&](realm::sync_session session, realm::sync_error error) { if (error.is_client_reset_requested()) { /* You might use this for reporting or to instruct the user to delete and re-install the app. */ }; }); auto syncedRealm = realm::db(syncConfig);
If the client reset operation cannot complete automatically, like when there are breaking schema changes, the client reset process falls through to the manual error handler. This may occur in any of these automatic client reset modes:
recover_unsynced_changes()
recover_or_discard_unsynced_changes()
discard_unsynced_changes()
We recommend treating the manual handler as a tool for fatal error recovery situations where you advise users to update the app or perform some other action.
Test Client Reset Handling
You can manually test your application's client reset handling by terminating and re-enabling Device Sync.
When you terminate and re-enable Sync, clients that have previously connected with Sync are unable to connect until after they perform a client reset. Terminating Sync deletes the metadata from the server that allows the client to synchronize. The client must download a new copy of the realm from the server. The server sends a client reset error to these clients. So, when you terminate Sync, you trigger the client reset condition.
To test client reset handling:
Write data from a client application and wait for it to synchronize.
Terminate and re-enable Device Sync.
Run the client app again. The app should get a client reset error when it tries to connect to the server.
Warning
While you iterate on client reset handling in your client application, you may need to terminate and re-enable Sync repeatedly. Terminating and re-enabling Sync renders all existing clients unable to sync until after completing a client reset. To avoid this in production, test client reset handling in a development environment.