-
Notifications
You must be signed in to change notification settings - Fork 171
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
RCORE-2185 Sync client should steal file ident of fresh realm when performing client reset #7850
Changes from 15 commits
520faad
b1b1dd9
5e1e1e9
73ad369
f5e1044
1d3ca5b
4b02363
9c38925
3a2af9a
aae37b7
6842197
2ddca98
86ca88a
005fea6
705a114
59a7b51
5072e32
1e51055
427d8fe
c82d03b
bacdd57
49ec153
f5569c1
043a470
f7d915c
49a5016
aa50680
d88f2e4
85abf4b
6a426a3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -285,12 +285,27 @@ inline std::ostream& operator<<(std::ostream& os, ConnectionState state) | |
|
||
// The reason a synchronization session is used for. | ||
enum class SessionReason { | ||
danieltabacaru marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Regular synchronization | ||
// Regular synchronization => BIND message sessionReason = 0 | ||
Sync = 0, | ||
// Download a fresh realm | ||
ClientReset, | ||
// Download a fresh realm => BIND message sessionReason = 1 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems to be changing this type to conflate two completely unrelated bits of state. This enum is naming the values passed to the server for the session reason, and adding other states doesn't make much sense. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated - now this enum is back to the original 2 values and a separate bool member variable ( The original SessionReason couldn't be used as-is since it is set to |
||
FreshRealm, | ||
// Client reset diff session => BIND message sessionReason = 1 | ||
ClientResetDiff, | ||
}; | ||
|
||
inline std::ostream& operator<<(std::ostream& os, SessionReason reason) | ||
{ | ||
switch (reason) { | ||
case SessionReason::Sync: | ||
return os << "Sync"; | ||
case SessionReason::FreshRealm: | ||
return os << "FreshRealm"; | ||
case SessionReason::ClientResetDiff: | ||
return os << "ClientResetDiff"; | ||
} | ||
REALM_TERMINATE("Invalid SessionReason value"); | ||
} | ||
|
||
} // namespace realm::sync | ||
|
||
#endif // REALM_SYNC_CLIENT_BASE_HPP |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1704,7 +1704,8 @@ void Session::activate() | |
bool file_exists = util::File::exists(get_realm_path()); | ||
m_performing_client_reset = get_client_reset_config().has_value(); | ||
|
||
logger.info("client_reset_config = %1, Realm exists = %2 ", m_performing_client_reset, file_exists); | ||
logger.info("client_reset_config = %1, Realm exists = %2, session_reason = %3 ", m_performing_client_reset, | ||
file_exists, get_session_reason()); | ||
if (!m_performing_client_reset) { | ||
danieltabacaru marked this conversation as resolved.
Show resolved
Hide resolved
|
||
get_history().get_status(m_last_version_available, m_client_file_ident, m_progress); // Throws | ||
} | ||
|
@@ -1717,7 +1718,7 @@ void Session::activate() | |
REALM_ASSERT_3(m_last_version_available, >=, m_progress.upload.client_version); | ||
init_progress_handler(); | ||
|
||
logger.debug("last_version_available = %1", m_last_version_available); // Throws | ||
logger.debug("last_version_available = %1", m_last_version_available); // Throws | ||
logger.debug("progress_download_server_version = %1", m_progress.download.server_version); // Throws | ||
logger.debug("progress_download_client_version = %1", | ||
m_progress.download.last_integrated_client_version); // Throws | ||
|
@@ -1887,14 +1888,20 @@ void Session::send_message() | |
return false; | ||
} | ||
|
||
return m_upload_progress.client_version >= m_pending_flx_sub_set->snapshot_version; | ||
// Send QUERY messages when the upload progress client version reaches the snapshot version | ||
// of a pending subscription, or if this is a fresh realm download session, since UPLOAD | ||
// messages are not allowed and the upload progress will not be updated. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the local upload progress is actually updated, but no upload messages are sent |
||
return m_upload_progress.client_version >= m_pending_flx_sub_set->snapshot_version || | ||
REALM_UNLIKELY(is_fresh_realm_download()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm pretty sure this logic is invalid? We can't reorder things like this because the pending subscription may depend on the writes before it which are waiting to be uploaded. We don't want to send Pending subscriptions to the server when downloading a fresh Realm; we want to send the currently active one. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is only for the fresh realm, so there are no meaningful writes. The fresh realm bootstraps using the active subscriptions of the local realm (but they are marked pending when committed to the fresh realm) so I think it's safe to assume that any changes before that were uploaded because otherwise we would have had the same problem when bootstrapping the local realm. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The fresh realm sync session has its own (new) subscription store with only one pending subscription (other than query version 0), which is the active subscription from the local realm. Since we're not sending upload messages, we can't rely on the |
||
}; | ||
|
||
if (check_pending_flx_version()) { | ||
return send_query_change_message(); // throws | ||
} | ||
|
||
if (m_allow_upload && (m_last_version_available > m_upload_progress.client_version)) { | ||
// Don't allow UPLOAD messages for client reset fresh realm download sessions | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not really doing anything in that sense to justify the comment |
||
if (REALM_LIKELY(!is_fresh_realm_download()) && m_allow_upload && | ||
(m_last_version_available > m_upload_progress.client_version)) { | ||
return send_upload_message(); // Throws | ||
} | ||
} | ||
|
@@ -1905,7 +1912,7 @@ void Session::send_bind_message() | |
REALM_ASSERT_EX(m_state == Active, m_state); | ||
|
||
session_ident_type session_ident = m_ident; | ||
bool need_client_file_ident = !have_client_file_ident(); | ||
bool need_client_file_ident = !have_client_file_ident() && !m_performing_client_reset; | ||
const bool is_subserver = false; | ||
|
||
|
||
|
@@ -1919,7 +1926,19 @@ void Session::send_bind_message() | |
if (auto migrated_partition = get_migration_store()->get_migrated_partition()) { | ||
bind_json_data["migratedPartition"] = *migrated_partition; | ||
} | ||
bind_json_data["sessionReason"] = static_cast<uint64_t>(get_session_reason()); | ||
auto xlate_session_reason = [](SessionReason reason) -> uint64_t { | ||
switch (reason) { | ||
case SessionReason::FreshRealm: | ||
[[fallthrough]]; | ||
case SessionReason::ClientResetDiff: | ||
return 1; | ||
case SessionReason::Sync: | ||
[[fallthrough]]; | ||
default: | ||
return 0; | ||
} | ||
}; | ||
bind_json_data["sessionReason"] = xlate_session_reason(get_session_reason()); | ||
auto schema_version = get_schema_version(); | ||
// Send 0 if schema is not versioned. | ||
bind_json_data["schemaVersion"] = schema_version != uint64_t(-1) ? schema_version : 0; | ||
|
@@ -2155,6 +2174,8 @@ void Session::send_upload_message() | |
locked_server_version); // Throws | ||
m_conn.initiate_write_message(out, this); // Throws | ||
|
||
call_debug_hook(SyncClientHookEvent::UploadMessageSent); | ||
|
||
// Other messages may be waiting to be sent | ||
enlist_to_send(); // Throws | ||
} | ||
|
@@ -2265,29 +2286,56 @@ bool Session::client_reset_if_needed() | |
return false; | ||
} | ||
|
||
// Save a copy of the status and action in case an error/exception occurs | ||
Status cr_status = client_reset_config->error; | ||
ProtocolErrorInfo::Action cr_action = client_reset_config->action; | ||
|
||
auto on_flx_version_complete = [this](int64_t version) { | ||
this->on_flx_sync_version_complete(version); | ||
}; | ||
bool did_reset = | ||
client_reset::perform_client_reset(logger, *get_db(), std::move(*client_reset_config), m_client_file_ident, | ||
get_flx_subscription_store(), on_flx_version_complete); | ||
|
||
call_debug_hook(SyncClientHookEvent::ClientResetMergeComplete); | ||
if (!did_reset) { | ||
try { | ||
// Storage for the file ident from the fresh realm that was migrated to the local realm | ||
SaltedFileIdent client_file_ident_out; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be better to reread the ident from the file rather than using an out parameter. This would make it harder to accidentally have inconsistent states where we fail to actually update the ident in the file and don't notice. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated - the |
||
bool did_reset = client_reset::perform_client_reset(logger, *get_db(), std::move(*client_reset_config), | ||
client_file_ident_out, get_flx_subscription_store(), | ||
on_flx_version_complete); | ||
m_client_file_ident = client_file_ident_out; | ||
|
||
call_debug_hook(SyncClientHookEvent::ClientResetMergeComplete); | ||
if (!did_reset) { | ||
return false; | ||
} | ||
} | ||
catch (const std::exception& e) { | ||
auto err_msg = util::format("A fatal error occurred during '%1' client reset diff for %2: '%3'", cr_action, | ||
cr_status, e.what()); | ||
logger.error(err_msg.c_str()); | ||
SessionErrorInfo err_info(Status{ErrorCodes::AutoClientResetFailed, err_msg}, IsFatal{true}); | ||
suspend(err_info); | ||
return false; | ||
} | ||
|
||
// The fresh Realm has been used to reset the state | ||
logger.debug("Client reset is completed, path=%1", get_realm_path()); // Throws | ||
logger.debug("Client reset is completed, path = %1", get_realm_path()); // Throws | ||
logger.debug("client_file_ident = %1, client_file_ident_salt = %2", m_client_file_ident.ident, | ||
m_client_file_ident.salt); // Throws | ||
|
||
SaltedFileIdent client_file_ident; | ||
get_history().get_status(m_last_version_available, client_file_ident, m_progress); // Throws | ||
// Print the version/progress information before performing the asserts | ||
logger.debug("last_version_available = %1", m_last_version_available); // Throws | ||
logger.debug("upload_progress_client_version = %1, upload_progress_server_version = %2", | ||
m_progress.upload.client_version, | ||
m_progress.upload.last_integrated_server_version); // Throws | ||
logger.debug("download_progress_client_version = %1, download_progress_server_version = %2", | ||
m_progress.download.last_integrated_client_version, | ||
m_progress.download.server_version); // Throws | ||
|
||
REALM_ASSERT_3(m_client_file_ident.ident, ==, client_file_ident.ident); | ||
REALM_ASSERT_3(m_client_file_ident.salt, ==, client_file_ident.salt); | ||
REALM_ASSERT_EX(m_progress.download.last_integrated_client_version == 0, | ||
m_progress.download.last_integrated_client_version); | ||
REALM_ASSERT_EX(m_progress.upload.client_version == 0, m_progress.upload.client_version); | ||
logger.trace("last_version_available = %1", m_last_version_available); // Throws | ||
|
||
m_upload_progress = m_progress.upload; | ||
m_download_progress = m_progress.download; | ||
|
@@ -2325,13 +2373,24 @@ Status Session::receive_ident_message(SaltedFileIdent client_file_ident) | |
bool legal_at_this_time = (m_bind_message_sent && !have_client_file_ident() && !m_error_message_received && | ||
!m_unbound_message_received); | ||
if (REALM_UNLIKELY(!legal_at_this_time)) { | ||
return {ErrorCodes::SyncProtocolInvariantFailed, "Received IDENT message when it was not legal"}; | ||
std::string_view illegal_reason; | ||
if (m_bind_message_sent) | ||
illegal_reason = "BIND message not sent"; | ||
else if (have_client_file_ident()) | ||
illegal_reason = "file ident has already been assigned to session"; | ||
else if (m_error_message_received) | ||
illegal_reason = "error message has been received from the server"; | ||
else if (m_unbound_message_received) | ||
illegal_reason = "session has already been suspended"; | ||
return {ErrorCodes::SyncProtocolInvariantFailed, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. did something happen during testing that required all these extra details? I think even with these extra messages we'd need a full log file to really see what's going on, and I don't know what the user is supposed to do with "BIND message not sent" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree - I just reverted these changes. |
||
util::format("Received IDENT message when it was not legal: %1", illegal_reason)}; | ||
} | ||
if (REALM_UNLIKELY(client_file_ident.ident < 1)) { | ||
return {ErrorCodes::SyncProtocolInvariantFailed, "Bad client file identifier in IDENT message"}; | ||
return {ErrorCodes::SyncProtocolInvariantFailed, | ||
util::format("Bad client file identifier in IDENT message: %1", client_file_ident.ident)}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. won't we see this in the log message above? |
||
} | ||
if (REALM_UNLIKELY(client_file_ident.salt == 0)) { | ||
return {ErrorCodes::SyncProtocolInvariantFailed, "Bad client file identifier salt in IDENT message"}; | ||
return {ErrorCodes::SyncProtocolInvariantFailed, "Bad client file identifier salt (0) in IDENT message"}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. won't we see this in the log message above? |
||
} | ||
|
||
m_client_file_ident = client_file_ident; | ||
|
@@ -2342,36 +2401,11 @@ Status Session::receive_ident_message(SaltedFileIdent client_file_ident) | |
return Status::OK(); // Success | ||
} | ||
|
||
// if a client reset happens, it will take care of setting the file ident | ||
// and if not, we do it here | ||
bool did_client_reset = false; | ||
|
||
// Save some of the client reset info for reporting to the client if an error occurs. | ||
Status cr_status(Status::OK()); // Start with no client reset | ||
ProtocolErrorInfo::Action cr_action = ProtocolErrorInfo::Action::NoAction; | ||
if (auto& cr_config = get_client_reset_config()) { | ||
cr_status = cr_config->error; | ||
cr_action = cr_config->action; | ||
} | ||
|
||
try { | ||
did_client_reset = client_reset_if_needed(); | ||
} | ||
catch (const std::exception& e) { | ||
auto err_msg = util::format("A fatal error occurred during '%1' client reset for %2: '%3'", cr_action, | ||
cr_status, e.what()); | ||
logger.error(err_msg.c_str()); | ||
SessionErrorInfo err_info(Status{ErrorCodes::AutoClientResetFailed, err_msg}, IsFatal{true}); | ||
suspend(err_info); | ||
return Status::OK(); | ||
} | ||
if (!did_client_reset) { | ||
get_history().set_client_file_ident(client_file_ident, | ||
m_fix_up_object_ids); // Throws | ||
m_progress.download.last_integrated_client_version = 0; | ||
m_progress.upload.client_version = 0; | ||
m_last_version_selected_for_upload = 0; | ||
} | ||
get_history().set_client_file_ident(client_file_ident, | ||
m_fix_up_object_ids); // Throws | ||
m_progress.download.last_integrated_client_version = 0; | ||
m_progress.upload.client_version = 0; | ||
m_last_version_selected_for_upload = 0; | ||
|
||
// Ready to send the IDENT message | ||
ensure_enlisted_to_send(); // Throws | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is more of an internal change than an enhancement. Changelog entries should tell users/sdk developers what the impact of changes is, and hopefully this change is invisible.