Skip to content

Commit 5533505

Browse files
Update server version on all entries in the sync history during client reset with recovery (#7291)
* Update server version on all entries in the sync history during client reset with recovery * Fix test * Changes after code review * Update changelog
1 parent 666ba2d commit 5533505

File tree

3 files changed

+70
-8
lines changed

3 files changed

+70
-8
lines changed

CHANGELOG.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
* Allow the query builder to construct >, >=, <, <= queries for string constants. This is a case sensitive lexicographical comparison. Improved performance of RQL (parsed) queries on a non-linked string property using: >, >=, <, <=, operators and fixed behaviour that a null string should be evaulated as less than everything, previously nulls were not matched. ([#3939](https://github.com/realm/realm-core/issues/3939), this is a prerequisite for https://github.com/realm/realm-swift/issues/8008).
66

77
### Fixed
8-
* <How do the end-user experience this issue? what was the impact?> ([#????](https://github.com/realm/realm-core/issues/????), since v?.?.?)
9-
* None.
8+
* Uploading the changesets recovered during an automatic client reset recovery may lead to 'Bad server version' errors and a new client reset. ([#7279](https://github.com/realm/realm-core/issues/7279), since v13.24.1)
109

1110
### Breaking changes
1211
* None.

src/realm/sync/noinst/client_history_impl.cpp

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ void ClientHistory::set_client_reset_adjustments(
6666
do_trim_sync_history(discard_count);
6767

6868
if (logger.would_log(util::Logger::Level::debug)) {
69-
logger.debug("Client reset adjustments: trimming %1 history entries and updating %2 of %3 remaining "
70-
"history entries:",
71-
discard_count, recovered_changesets.size(), sync_history_size());
69+
logger.debug("Client reset adjustments: trimming %1 history entries and updating the remaining history "
70+
"entries (%2)",
71+
discard_count, sync_history_size());
7272
for (size_t i = 0, size = m_arrays->changesets.size(); i < size; ++i) {
7373
logger.debug("- %1: ident(%2) changeset_size(%3) remote_version(%4)", i,
7474
m_arrays->origin_file_idents.get(i), m_arrays->changesets.get(i).size(),
@@ -83,10 +83,15 @@ void ClientHistory::set_client_reset_adjustments(
8383
auto i = size_t(version - m_sync_history_base_version);
8484
util::compression::allocate_and_compress_nonportable(arena, changeset, compressed);
8585
m_arrays->changesets.set(i, BinaryData{compressed.data(), compressed.size()}); // Throws
86-
m_arrays->remote_versions.set(i, server_version.version);
8786
m_arrays->reciprocal_transforms.set(i, BinaryData());
88-
logger.debug("Updating %1: client_version(%2) changeset_size(%3) server_version(%4)", i, version,
89-
compressed.size(), server_version.version);
87+
}
88+
// Server version is updated for *every* entry in the sync history to ensure that server versions don't
89+
// decrease.
90+
for (size_t i = 0, size = m_arrays->remote_versions.size(); i < size; ++i) {
91+
m_arrays->remote_versions.set(i, server_version.version);
92+
version_type version = m_sync_history_base_version + i;
93+
logger.debug("Updating %1: client_version(%2) changeset_size(%3) server_version(%4)", i, version + 1,
94+
m_arrays->changesets.get(i).size(), server_version.version);
9095
}
9196
}
9297
logger.debug("New uploadable bytes after client reset adjustment: %1", uploadable_bytes);

test/object-store/sync/flx_sync.cpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,7 @@ TEST_CASE("flx: client reset", "[sync][flx][client reset][baas]") {
582582
config_remote.path += ".remote";
583583
const std::string str_field_value = "foo";
584584
const int64_t local_added_int = 100;
585+
const int64_t local_added_int2 = 150;
585586
const int64_t remote_added_int = 200;
586587
size_t before_reset_count = 0;
587588
size_t after_reset_count = 0;
@@ -745,6 +746,63 @@ TEST_CASE("flx: client reset", "[sync][flx][client reset][baas]") {
745746
->run();
746747
}
747748

749+
SECTION("Recover: offline writes interleaved with subscriptions and empty writes") {
750+
config_local.sync_config->client_resync_mode = ClientResyncMode::Recover;
751+
auto&& [reset_future, reset_handler] = make_client_reset_handler();
752+
config_local.sync_config->notify_after_client_reset = reset_handler;
753+
auto test_reset = reset_utils::make_baas_flx_client_reset(config_local, config_remote, harness.session());
754+
test_reset
755+
->make_local_changes([&](SharedRealm local_realm) {
756+
// The sequence of events bellow generates five changesets:
757+
// 1. create sub1 => empty changeset
758+
// 2. create local_added_int object
759+
// 3. create empty changeset
760+
// 4. create sub2 => empty changeset
761+
// 5. create local_added_int2 object
762+
//
763+
// Before sending 'sub2' to the server, an UPLOAD message is sent first.
764+
// The upload message contains changeset 2. (local_added_int) with the cursor
765+
// of changeset 3. (empty changeset).
766+
767+
add_subscription_for_new_object(local_realm, str_field_value, local_added_int);
768+
// Commit empty changeset.
769+
local_realm->begin_transaction();
770+
local_realm->commit_transaction();
771+
add_subscription_for_new_object(local_realm, str_field_value, local_added_int2);
772+
})
773+
->make_remote_changes([&](SharedRealm remote_realm) {
774+
add_subscription_for_new_object(remote_realm, str_field_value, remote_added_int);
775+
sync::SubscriptionSet::State actual =
776+
remote_realm->get_latest_subscription_set()
777+
.get_state_change_notification(sync::SubscriptionSet::State::Complete)
778+
.get();
779+
REQUIRE(actual == sync::SubscriptionSet::State::Complete);
780+
})
781+
->on_post_reset([&, client_reset_future = std::move(reset_future)](SharedRealm local_realm) {
782+
ClientResyncMode mode = client_reset_future.get();
783+
REQUIRE(mode == ClientResyncMode::Recover);
784+
auto subs = local_realm->get_latest_subscription_set();
785+
subs.get_state_change_notification(sync::SubscriptionSet::State::Complete).get();
786+
// make sure that the subscription for "foo" survived the reset
787+
size_t count_of_foo = count_queries_with_str(subs, util::format("\"%1\"", str_field_value));
788+
subs.refresh();
789+
REQUIRE(subs.state() == sync::SubscriptionSet::State::Complete);
790+
REQUIRE(count_of_foo == 1);
791+
local_realm->refresh();
792+
auto table = local_realm->read_group().get_table("class_TopLevel");
793+
auto str_col = table->get_column_key("queryable_str_field");
794+
auto int_col = table->get_column_key("queryable_int_field");
795+
auto tv = table->where().equal(str_col, StringData(str_field_value)).find_all();
796+
tv.sort(int_col);
797+
// the objects we created while offline was recovered, and the remote object was downloaded
798+
REQUIRE(tv.size() == 3);
799+
CHECK(tv.get_object(0).get<Int>(int_col) == local_added_int);
800+
CHECK(tv.get_object(1).get<Int>(int_col) == local_added_int2);
801+
CHECK(tv.get_object(2).get<Int>(int_col) == remote_added_int);
802+
})
803+
->run();
804+
}
805+
748806
auto validate_integrity_of_arrays = [](TableRef table) -> size_t {
749807
auto sum_col = table->get_column_key("sum_of_list_field");
750808
auto array_col = table->get_column_key("list_of_ints_field");

0 commit comments

Comments
 (0)