From 0c081b46abe168b6b142a5574c6e6f99fe8c8bf9 Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Wed, 12 Jul 2023 15:35:28 -0400 Subject: [PATCH 01/30] big shabang --- src/realm.h | 10 +- src/realm/error_codes.cpp | 32 ++ src/realm/error_codes.h | 17 + src/realm/error_codes.hpp | 17 + src/realm/impl/simulated_failure.hpp | 21 +- src/realm/object-store/audit.mm | 5 +- .../object-store/c_api/socket_provider.cpp | 11 +- src/realm/object-store/c_api/sync.cpp | 13 +- src/realm/object-store/sync/sync_session.cpp | 150 ++----- src/realm/object-store/sync/sync_session.hpp | 2 +- src/realm/status.hpp | 5 + src/realm/sync/client.cpp | 13 +- src/realm/sync/client_base.hpp | 24 +- src/realm/sync/config.cpp | 48 +- src/realm/sync/config.hpp | 16 +- src/realm/sync/network/default_socket.cpp | 49 +- src/realm/sync/network/websocket.cpp | 37 +- src/realm/sync/network/websocket.hpp | 34 +- src/realm/sync/noinst/client_history_impl.cpp | 29 +- src/realm/sync/noinst/client_history_impl.hpp | 16 +- src/realm/sync/noinst/client_impl_base.cpp | 423 ++++++++---------- src/realm/sync/noinst/client_impl_base.hpp | 32 +- src/realm/sync/noinst/protocol_codec.hpp | 72 ++- src/realm/sync/noinst/server/server.cpp | 32 +- src/realm/sync/protocol.cpp | 111 +++++ src/realm/sync/protocol.hpp | 97 ++-- src/realm/sync/socket_provider.hpp | 10 +- test/object-store/c_api/c_api.cpp | 5 +- test/object-store/sync/app.cpp | 10 +- test/object-store/sync/client_reset.cpp | 19 +- test/object-store/sync/flx_migration.cpp | 4 +- test/object-store/sync/flx_sync.cpp | 42 +- test/object-store/sync/session/session.cpp | 36 +- .../sync/session/wait_for_completion.cpp | 7 +- test/sync_fixtures.hpp | 13 +- test/test_client_reset.cpp | 11 +- test/test_sync.cpp | 131 ++---- test/test_util_websocket.cpp | 7 +- 38 files changed, 774 insertions(+), 837 deletions(-) diff --git a/src/realm.h b/src/realm.h index b7ba825c3f0..9db6734b178 100644 --- a/src/realm.h +++ b/src/realm.h @@ -3864,13 +3864,13 @@ RLM_API void realm_sync_session_wait_for_upload_completion(realm_sync_session_t* /** * Wrapper for SyncSession::OnlyForTesting::handle_error. This routine should be used only for testing. * @param session ptr to a valid sync session - * @param error_code error code to simulate - * @param category category of the error to simulate - * @param error_message string representing the error + * @param error_code realm_errno_e representing the error to simulate + * @param error_str error message to be included with Status * @param is_fatal boolean to signal if the error is fatal or not */ -RLM_API void realm_sync_session_handle_error_for_testing(const realm_sync_session_t* session, int error_code, - int category, const char* error_message, bool is_fatal); +RLM_API void realm_sync_session_handle_error_for_testing(const realm_sync_session_t* session, + realm_errno_e error_code, const char* error_str, + bool is_fatal); /** * In case of exception thrown in user code callbacks, this api will allow the sdk to store the user code exception diff --git a/src/realm/error_codes.cpp b/src/realm/error_codes.cpp index 9db4c4db0be..6b28b261e17 100644 --- a/src/realm/error_codes.cpp +++ b/src/realm/error_codes.cpp @@ -50,6 +50,18 @@ ErrorCategory ErrorCodes::error_categories(Error code) case SubscriptionFailed: case UnsupportedFileFormatVersion: case OperationAborted: + case AutoClientResetFailed: + case ConnectionClosed: + case SyncClientResetRequired: + case SyncCompensatingWrite: + case SyncConnectFailed: + case SyncPermissionDenied: + case SyncProtocolInvariantFailed: + case SyncProtocolNegotiationFailed: + case SyncServerPermissionsChanged: + case SyncUserMismatch: + case TlsHandshakeFailed: + case SyncWriteNotAllowed: return ErrorCategory().set(ErrorCategory::runtime_error); case DecryptionFailed: @@ -116,6 +128,10 @@ ErrorCategory ErrorCodes::error_categories(Error code) case TopLevelObject: case TypeMismatch: case UnexpectedPrimaryKey: + case BadSyncPartitionValue: + case InvalidSubscriptionQuery: + case SyncInvalidSchemaChange: + case WrongSyncType: return ErrorCategory().set(ErrorCategory::invalid_argument).set(ErrorCategory::logic_error); case CustomError: @@ -238,10 +254,12 @@ static const MapElem string_to_error_code[] = { {"AuthProviderAlreadyExists", ErrorCodes::AuthProviderAlreadyExists}, {"AuthProviderDuplicateName", ErrorCodes::AuthProviderDuplicateName}, {"AuthProviderNotFound", ErrorCodes::AuthProviderNotFound}, + {"AutoClientResetFailed", ErrorCodes::AutoClientResetFailed}, {"BadBsonParse", ErrorCodes::BadBsonParse}, {"BadChangeset", ErrorCodes::BadChangeset}, {"BadRequest", ErrorCodes::BadRequest}, {"BadServerUrl", ErrorCodes::BadServerUrl}, + {"BadSyncPartitionValue", ErrorCodes::BadSyncPartitionValue}, {"BadToken", ErrorCodes::BadToken}, {"BadVersion", ErrorCodes::BadVersion}, {"BrokenInvariant", ErrorCodes::BrokenInvariant}, @@ -253,6 +271,7 @@ static const MapElem string_to_error_code[] = { {"ClientUserNotFound", ErrorCodes::ClientUserNotFound}, {"ClientUserNotLoggedIn", ErrorCodes::ClientUserNotLoggedIn}, {"ClosedRealm", ErrorCodes::ClosedRealm}, + {"ConnectionClosed", ErrorCodes::ConnectionClosed}, {"CrossTableLinkTarget", ErrorCodes::CrossTableLinkTarget}, {"CustomError", ErrorCodes::CustomError}, {"DecryptionFailed", ErrorCodes::DecryptionFailed}, @@ -297,6 +316,7 @@ static const MapElem string_to_error_code[] = { {"InvalidServerResponse", ErrorCodes::InvalidServerResponse}, {"InvalidSession", ErrorCodes::InvalidSession}, {"InvalidSortDescriptor", ErrorCodes::InvalidSortDescriptor}, + {"InvalidSubscriptionQuery", ErrorCodes::InvalidSubscriptionQuery}, {"InvalidTableRef", ErrorCodes::InvalidTableRef}, {"InvalidatedObject", ErrorCodes::InvalidatedObject}, {"KeyAlreadyUsed", ErrorCodes::KeyAlreadyUsed}, @@ -351,9 +371,20 @@ static const MapElem string_to_error_code[] = { {"ServiceTypeNotFound", ErrorCodes::ServiceTypeNotFound}, {"StaleAccessor", ErrorCodes::StaleAccessor}, {"SubscriptionFailed", ErrorCodes::SubscriptionFailed}, + {"SyncClientResetRequired", ErrorCodes::SyncClientResetRequired}, + {"SyncCompensatingWrite", ErrorCodes::SyncCompensatingWrite}, + {"SyncConnectFailed", ErrorCodes::SyncConnectFailed}, + {"SyncInvalidSchemaChange", ErrorCodes::SyncInvalidSchemaChange}, + {"SyncPermissionDenied", ErrorCodes::SyncPermissionDenied}, + {"SyncProtocolInvariantFailed", ErrorCodes::SyncProtocolInvariantFailed}, + {"SyncProtocolNegotiationFailed", ErrorCodes::SyncProtocolNegotiationFailed}, + {"SyncServerPermissionsChanged", ErrorCodes::SyncServerPermissionsChanged}, + {"SyncUserMismatch", ErrorCodes::SyncUserMismatch}, + {"SyncWriteNotAllowed", ErrorCodes::SyncWriteNotAllowed}, {"SyntaxError", ErrorCodes::SyntaxError}, {"SystemError", ErrorCodes::SystemError}, {"TableNameInUse", ErrorCodes::TableNameInUse}, + {"TlsHandshakeFailed", ErrorCodes::TlsHandshakeFailed}, {"TopLevelObject", ErrorCodes::TopLevelObject}, {"TwilioError", ErrorCodes::TwilioError}, {"TypeMismatch", ErrorCodes::TypeMismatch}, @@ -370,6 +401,7 @@ static const MapElem string_to_error_code[] = { {"WebSocketConnectionClosedClientError", ErrorCodes::WebSocketConnectionClosedClientError}, {"WebSocketConnectionClosedServerError", ErrorCodes::WebSocketConnectionClosedServerError}, {"WebSocketResolveFailedError", ErrorCodes::WebSocketResolveFailedError}, + {"WrongSyncType", ErrorCodes::WrongSyncType}, {"WrongThread", ErrorCodes::WrongThread}, {"WrongTransactionState", ErrorCodes::WrongTransactionState}, }; diff --git a/src/realm/error_codes.h b/src/realm/error_codes.h index ec265c75796..6eb2b2b6ae7 100644 --- a/src/realm/error_codes.h +++ b/src/realm/error_codes.h @@ -65,6 +65,23 @@ typedef enum realm_errno { RLM_ERR_NO_SUBSCRIPTION_FOR_WRITE = 1026, RLM_ERR_OPERATION_ABORTED = 1027, + RLM_ERR_AUTO_CLIENT_RESET_FAILED = 1028, + RLM_ERR_BAD_SYNC_PARTITION_VALUE = 1029, + RLM_ERR_CONNECTION_CLOSED = 1030, + RLM_ERR_INVALID_SUBSCRIPTION_QUERY = 1031, + RLM_ERR_SYNC_CLIENT_RESET_REQUIRED = 1032, + RLM_ERR_SYNC_COMPENSATING_WRITE = 1033, + RLM_ERR_SYNC_CONNECT_FAILED = 1034, + RLM_ERR_SYNC_INVALID_SCHEMA_CHANGE = 1035, + RLM_ERR_SYNC_PERMISSION_DENIED = 1036, + RLM_ERR_SYNC_PROTOCOL_INVARIANT_FAILED = 1037, + RLM_ERR_SYNC_PROTOCOL_NEGOTIATION_FAILED = 1038, + RLM_ERR_SYNC_SERVER_PERMISSIONS_CHANGED = 1039, + RLM_ERR_SYNC_USER_MISMATCH = 1040, + RLM_ERR_TLS_HANDSHAKE_FAILED = 1041, + RLM_ERR_WRONG_SYNC_TYPE = 1042, + RLM_ERR_SYNC_WRITE_NOT_ALLOWED = 1043, + RLM_ERR_SYSTEM_ERROR = 1999, RLM_ERR_LOGIC = 2000, diff --git a/src/realm/error_codes.hpp b/src/realm/error_codes.hpp index 73dbe090149..69c3438f680 100644 --- a/src/realm/error_codes.hpp +++ b/src/realm/error_codes.hpp @@ -108,6 +108,23 @@ class ErrorCodes { BadVersion = RLM_ERR_BAD_VERSION, OperationAborted = RLM_ERR_OPERATION_ABORTED, + AutoClientResetFailed = RLM_ERR_AUTO_CLIENT_RESET_FAILED, + BadSyncPartitionValue = RLM_ERR_BAD_SYNC_PARTITION_VALUE, + ConnectionClosed = RLM_ERR_CONNECTION_CLOSED, + InvalidSubscriptionQuery = RLM_ERR_INVALID_SUBSCRIPTION_QUERY, + SyncClientResetRequired = RLM_ERR_SYNC_CLIENT_RESET_REQUIRED, + SyncCompensatingWrite = RLM_ERR_SYNC_COMPENSATING_WRITE, + SyncConnectFailed = RLM_ERR_SYNC_CONNECT_FAILED, + SyncInvalidSchemaChange = RLM_ERR_SYNC_INVALID_SCHEMA_CHANGE, + SyncPermissionDenied = RLM_ERR_SYNC_PERMISSION_DENIED, + SyncProtocolInvariantFailed = RLM_ERR_SYNC_PROTOCOL_INVARIANT_FAILED, + SyncProtocolNegotiationFailed = RLM_ERR_SYNC_PROTOCOL_NEGOTIATION_FAILED, + SyncServerPermissionsChanged = RLM_ERR_SYNC_SERVER_PERMISSIONS_CHANGED, + SyncUserMismatch = RLM_ERR_SYNC_USER_MISMATCH, + TlsHandshakeFailed = RLM_ERR_TLS_HANDSHAKE_FAILED, + WrongSyncType = RLM_ERR_WRONG_SYNC_TYPE, + SyncWriteNotAllowed = RLM_ERR_SYNC_WRITE_NOT_ALLOWED, + SystemError = RLM_ERR_SYSTEM_ERROR, LogicError = RLM_ERR_LOGIC, diff --git a/src/realm/impl/simulated_failure.hpp b/src/realm/impl/simulated_failure.hpp index ab65f01d77b..784f8c9fa0c 100644 --- a/src/realm/impl/simulated_failure.hpp +++ b/src/realm/impl/simulated_failure.hpp @@ -22,6 +22,7 @@ #include #include +#include #include #ifdef REALM_DEBUG @@ -31,7 +32,7 @@ namespace realm { namespace _impl { -class SimulatedFailure : public std::system_error { +class SimulatedFailure : public SystemError { public: enum FailureType { generic, @@ -64,11 +65,6 @@ class SimulatedFailure : public std::system_error { /// not defined, this function always return false. static bool check_trigger(FailureType) noexcept; - /// The specified error code is set to `make_error_code(failure_type)` if - /// check_trigger() returns true. Otherwise it is set to - /// `std::error_code()`. Returns a copy of the updated error code. - static std::error_code trigger(FailureType failure_type, std::error_code&) noexcept; - /// Throws SimulatedFailure if check_trigger() returns true. The exception /// will be constructed with an error code equal to /// `make_error_code(failure_type)`. @@ -184,17 +180,6 @@ inline bool SimulatedFailure::check_trigger(FailureType failure_type) noexcept #endif } -inline std::error_code SimulatedFailure::trigger(FailureType failure_type, std::error_code& ec) noexcept -{ - if (check_trigger(failure_type)) { - ec = make_error_code(failure_type); - } - else { - ec = std::error_code(); - } - return ec; -} - inline void SimulatedFailure::trigger(FailureType failure_type) { if (check_trigger(failure_type)) @@ -220,7 +205,7 @@ inline void SimulatedFailure::set_thread_local(bool tl) } inline SimulatedFailure::SimulatedFailure(std::error_code ec) - : std::system_error(ec) + : SystemError(ec, {}) { } diff --git a/src/realm/object-store/audit.mm b/src/realm/object-store/audit.mm index 28a6bd58208..4f23366a2d8 100644 --- a/src/realm/object-store/audit.mm +++ b/src/realm/object-store/audit.mm @@ -786,7 +786,7 @@ bool write_event(Timestamp timestamp, StringData activity, StringData event_type if (status.get_std_error_code()) { m_logger->error("Events: Upload on '%1' failed with error '%2'.", path, status.reason()); if (m_error_handler) { - m_error_handler(SyncError(status.get_std_error_code(), status.reason(), false)); + m_error_handler(SyncError(status, false)); } } else { @@ -873,8 +873,7 @@ bool write_event(Timestamp timestamp, StringData activity, StringData event_type sync_config->error_handler = [error_handler = m_error_handler, weak_self = weak_from_this()](auto, SyncError error) { if (auto self = weak_self.lock()) { - self->m_logger->error("Events: Received sync error: %1 (ec=%2)", error.what(), - error.get_system_error().value()); + self->m_logger->error("Events: Received sync error: %1 (code=%2)", error.to_status()); } if (error_handler) { error_handler(error); diff --git a/src/realm/object-store/c_api/socket_provider.cpp b/src/realm/object-store/c_api/socket_provider.cpp index a43d4fa1a87..b922af2c1f9 100644 --- a/src/realm/object-store/c_api/socket_provider.cpp +++ b/src/realm/object-store/c_api/socket_provider.cpp @@ -122,9 +122,9 @@ struct CAPIWebSocketObserver : sync::WebSocketObserver { return m_observer->websocket_binary_message_received(data); } - bool websocket_closed_handler(bool was_clean, Status status) final + bool websocket_closed_handler(bool was_clean, sync::websocket::WebSocketError code, std::string_view msg) final { - return m_observer->websocket_closed_handler(was_clean, status); + return m_observer->websocket_closed_handler(was_clean, code, msg); } private: @@ -249,11 +249,8 @@ RLM_API void realm_sync_socket_websocket_message(realm_websocket_observer_t* rea RLM_API void realm_sync_socket_websocket_closed(realm_websocket_observer_t* realm_websocket_observer, bool was_clean, realm_web_socket_errno_e code, const char* reason) { - auto status = sync::websocket::WebSocketError(code); - auto closed_status = code == realm_web_socket_errno_e::RLM_ERR_WEBSOCKET_OK - ? Status::OK() - : Status{sync::websocket::make_error_code(status), reason}; - realm_websocket_observer->get()->websocket_closed_handler(was_clean, closed_status); + realm_websocket_observer->get()->websocket_closed_handler( + was_clean, static_cast(code), reason); } RLM_API void realm_sync_client_config_set_sync_socket(realm_sync_client_config_t* config, diff --git a/src/realm/object-store/c_api/sync.cpp b/src/realm/object-store/c_api/sync.cpp index 725287855ce..5def4277de3 100644 --- a/src/realm/object-store/c_api/sync.cpp +++ b/src/realm/object-store/c_api/sync.cpp @@ -886,15 +886,14 @@ RLM_API void realm_sync_session_wait_for_upload_completion(realm_sync_session_t* (*session)->wait_for_upload_completion(std::move(cb)); } -RLM_API void realm_sync_session_handle_error_for_testing(const realm_sync_session_t* session, int error_code, - int error_category, const char* error_message, bool is_fatal) +RLM_API void realm_sync_session_handle_error_for_testing(const realm_sync_session_t* session, + realm_errno_e error_code, const char* error_str, + bool is_fatal) { REALM_ASSERT(session); - realm_sync_error_code_t sync_error{static_cast(error_category), error_code, - error_message}; - std::error_code err; - sync_error_to_error_code(sync_error, &err); - SyncSession::OnlyForTesting::handle_error(*session->get(), sync::SessionErrorInfo{err, error_message, !is_fatal}); + SyncSession::OnlyForTesting::handle_error( + *session->get(), + sync::SessionErrorInfo{Status{static_cast(error_code), error_str}, !is_fatal}); } } // namespace realm::c_api diff --git a/src/realm/object-store/sync/sync_session.cpp b/src/realm/object-store/sync/sync_session.cpp index e03ce0dda22..64b4911b1e2 100644 --- a/src/realm/object-store/sync/sync_session.cpp +++ b/src/realm/object-store/sync/sync_session.cpp @@ -32,7 +32,7 @@ #include #include #include -#include +#include #include #include #include @@ -238,20 +238,19 @@ void SyncSession::become_waiting_for_access_token() m_state = State::WaitingForAccessToken; } -void SyncSession::handle_bad_auth(const std::shared_ptr& user, Status error_code, - std::string_view context_message) +void SyncSession::handle_bad_auth(const std::shared_ptr& user, Status status) { // TODO: ideally this would write to the logs as well in case users didn't set up their error handler. { util::CheckedUniqueLock lock(m_state_mutex); - cancel_pending_waits(std::move(lock), error_code); + cancel_pending_waits(std::move(lock), status); } if (user) { user->log_out(); } if (auto error_handler = config(&SyncConfig::error_handler)) { - auto user_facing_error = SyncError(realm::sync::ProtocolError::bad_authentication, context_message, true); + auto user_facing_error = SyncError(std::move(status), true); error_handler(shared_from_this(), std::move(user_facing_error)); } } @@ -301,21 +300,24 @@ SyncSession::handle_refresh(const std::shared_ptr& session, bool re // there was a problem locally before even sending the request to the server // eg. ClientErrorCode::user_not_found, ClientErrorCode::user_not_logged_in, // ClientErrorCode::too_many_redirects - session->handle_bad_auth(session_user, error->to_status(), error->reason()); + session->handle_bad_auth(session_user, error->to_status()); } else if (check_for_auth_failure(*error)) { // A 401 response on a refresh request means that the token cannot be refreshed and we should not // retry. This can be because an admin has revoked this user's sessions, the user has been disabled, // or the refresh token has expired according to the server's clock. - session->handle_bad_auth(session_user, error->to_status(), - "Unable to refresh the user access token."); + session->handle_bad_auth( + session_user, + {error->code(), util::format("Unable to refresh the user access token: %1", error->reason())}); } else if (check_for_redirect_response(*error)) { // A 301 or 308 response is an unhandled permanent redirect response (which should not happen) - if // this is received, fail the request with an appropriate error message. // Temporary redirect responses (302, 307) are not supported - session->handle_bad_auth(session_user, error->to_status(), - "Unhandled redirect response when trying to reach the server."); + session->handle_bad_auth( + session_user, + {error->code(), util::format("Unhandled redirect response when trying to reach the server: %1", + error->reason())}); } else { // A refresh request has failed. This is an unexpected non-fatal error and we would @@ -581,8 +583,9 @@ void SyncSession::handle_fresh_realm_downloaded(DBRef db, Status status, const bool try_again = false; sync::SessionErrorInfo synthetic( - make_error_code(sync::Client::Error::auto_client_reset_failure), - util::format("A fatal error occured during client reset: '%1'", status.reason()), try_again); + Status{ErrorCodes::AutoClientResetFailed, + util::format("A fatal error occured during client reset: '%1'", status.reason())}, + try_again); handle_error(synthetic); return; } @@ -633,27 +636,50 @@ void SyncSession::handle_error(sync::SessionErrorInfo error) { enum class NextStateAfterError { none, inactive, error }; auto next_state = error.is_fatal() ? NextStateAfterError::error : NextStateAfterError::none; - auto error_code = error.error_code; util::Optional delete_file; bool log_out_user = false; bool unrecognized_by_client = false; - if (error_code == make_error_code(sync::Client::Error::auto_client_reset_failure)) { + if (error.status == ErrorCodes::AutoClientResetFailed) { // At this point, automatic recovery has been attempted but it failed. // Fallback to a manual reset and let the user try to handle it. next_state = NextStateAfterError::inactive; delete_file = ShouldBackup::yes; } - else if (error_code.category() == realm::sync::protocol_error_category()) { + + if (error.websocket_error != sync::websocket::WebSocketError::websocket_ok) { + using WebSocketError = sync::websocket::WebSocketError; + auto websocket_error = error.websocket_error; + // The server replies with '401: unauthorized' if the access token is invalid, expired, revoked, or the + // user is disabled. In this scenario we attempt an automatic token refresh and if that succeeds continue + // as normal. If the refresh request also fails with 401 then we need to stop retrying and pass along the + // error; see handle_refresh(). + bool redirect_occurred = websocket_error == WebSocketError::websocket_moved_permanently; + if (redirect_occurred || websocket_error == WebSocketError::websocket_unauthorized || + websocket_error == WebSocketError::websocket_abnormal_closure) { + if (auto u = user()) { + // If a redirection occurred, the location metadata will be updated before refreshing the access + // token. + u->refresh_custom_data(redirect_occurred, handle_refresh(shared_from_this(), redirect_occurred)); + return; + } + } + } + + if (error.status == ErrorCodes::ConnectionClosed) { + // Not real errors, don't need to be reported to the SDK. + return; + } + + // Can be called from within SyncSession with an AuthError to handle app-level authentication problems. + if (error.status == ErrorCodes::AuthError) { + next_state = NextStateAfterError::inactive; + log_out_user = true; + } + + if (error.server_requests_action != sync::ProtocolErrorInfo::Action::NoAction) { switch (error.server_requests_action) { case sync::ProtocolErrorInfo::Action::NoAction: - // Although a protocol error, this is not sent by the server. - // Therefore, there is no action. - if (error_code == realm::sync::ProtocolError::bad_authentication) { - next_state = NextStateAfterError::inactive; - log_out_user = true; - break; - } REALM_UNREACHABLE(); // This is not sent by the MongoDB server case sync::ProtocolErrorInfo::Action::ApplicationBug: [[fallthrough]]; @@ -712,87 +738,9 @@ void SyncSession::handle_error(sync::SessionErrorInfo error) return; } } - else if (error_code.category() == realm::sync::client_error_category()) { - using ClientError = realm::sync::ClientError; - switch (static_cast(error_code.value())) { - case ClientError::connection_closed: - case ClientError::pong_timeout: - // Not real errors, don't need to be reported to the SDK. - return; - case ClientError::bad_changeset: - case ClientError::bad_changeset_header_syntax: - case ClientError::bad_changeset_size: - case ClientError::bad_client_file_ident: - case ClientError::bad_client_file_ident_salt: - case ClientError::bad_client_version: - case ClientError::bad_compression: - case ClientError::bad_error_code: - case ClientError::bad_file_ident: - case ClientError::bad_message_order: - case ClientError::bad_origin_file_ident: - case ClientError::bad_progress: - case ClientError::bad_protocol_from_server: - case ClientError::bad_request_ident: - case ClientError::bad_server_version: - case ClientError::bad_session_ident: - case ClientError::bad_state_message: - case ClientError::bad_syntax: - case ClientError::bad_timestamp: - case ClientError::client_too_new_for_server: - case ClientError::client_too_old_for_server: - case ClientError::connect_timeout: - case ClientError::limits_exceeded: - case ClientError::protocol_mismatch: - case ClientError::ssl_server_cert_rejected: - case ClientError::missing_protocol_feature: - case ClientError::unknown_message: - case ClientError::http_tunnel_failed: - case ClientError::auto_client_reset_failure: - // Don't do anything special for these errors. - // Future functionality may require special-case handling for existing - // errors, or newly introduced error codes. - break; - } - } - else if (error_code.category() == sync::websocket::websocket_error_category()) { - using WebSocketError = sync::websocket::WebSocketError; - auto websocket_error = static_cast(error_code.value()); - - // The server replies with '401: unauthorized' if the access token is invalid, expired, revoked, or the user - // is disabled. In this scenario we attempt an automatic token refresh and if that succeeds continue as - // normal. If the refresh request also fails with 401 then we need to stop retrying and pass along the error; - // see handle_refresh(). - bool redirect_occurred = websocket_error == WebSocketError::websocket_moved_permanently; - if (redirect_occurred || websocket_error == WebSocketError::websocket_unauthorized || - websocket_error == WebSocketError::websocket_abnormal_closure) { - if (auto u = user()) { - // If a redirection occurred, the location metadata will be updated before refreshing the access - // token. - u->refresh_custom_data(redirect_occurred, handle_refresh(shared_from_this(), redirect_occurred)); - return; - } - } - - // If the websocket was closed cleanly or if the socket disappeared, don't notify the user as an error - // since the sync client will retry. - if (websocket_error == WebSocketError::websocket_read_error || - websocket_error == WebSocketError::websocket_write_error) { - return; - } - - // Surface a simplified websocket error to the user. - auto simplified_error = sync::websocket::get_simplified_websocket_error(websocket_error); - std::error_code new_error_code(simplified_error, sync::websocket::websocket_error_category()); - error = sync::SessionErrorInfo(new_error_code, error.message, error.try_again); - } - else { - // Unrecognized error code. - unrecognized_by_client = true; - } util::CheckedUniqueLock lock(m_state_mutex); - SyncError sync_error{error.error_code, std::string(error.message), error.is_fatal(), error.log_url, - std::move(error.compensating_writes)}; + SyncError sync_error{error.status, error.is_fatal(), error.log_url, std::move(error.compensating_writes)}; // `action` is used over `shouldClientReset` and `isRecoveryModeDisabled`. sync_error.server_requests_action = error.server_requests_action; sync_error.is_unrecognized_by_client = unrecognized_by_client; diff --git a/src/realm/object-store/sync/sync_session.hpp b/src/realm/object-store/sync/sync_session.hpp index 423898de5a1..ad4a599f703 100644 --- a/src/realm/object-store/sync/sync_session.hpp +++ b/src/realm/object-store/sync/sync_session.hpp @@ -383,7 +383,7 @@ class SyncSession : public std::enable_shared_from_this { sync::ProtocolErrorInfo::Action server_requests_action) REQUIRES(!m_state_mutex, !m_config_mutex, !m_connection_state_mutex); void handle_error(sync::SessionErrorInfo) REQUIRES(!m_state_mutex, !m_config_mutex, !m_connection_state_mutex); - void handle_bad_auth(const std::shared_ptr& user, Status error_code, std::string_view context_message) + void handle_bad_auth(const std::shared_ptr& user, Status status) REQUIRES(!m_state_mutex, !m_config_mutex); // If sub_notify_error is set (including Status::OK()), then the pending subscription waiters will // also be called with the sub_notify_error status value. diff --git a/src/realm/status.hpp b/src/realm/status.hpp index d8e50bea1ef..015a748197a 100644 --- a/src/realm/status.hpp +++ b/src/realm/status.hpp @@ -73,6 +73,11 @@ class REALM_NODISCARD Status { inline ErrorCodes::Error code() const noexcept; inline std::string_view code_string() const noexcept; + bool is_same_as(const Status& other) const noexcept + { + return m_error == other.m_error; + } + /* * This class is marked nodiscard so that we always handle errors. If there is a place where we need * to explicitly ignore an error, you can call this function, which does nothing, to satisfy the compiler. diff --git a/src/realm/sync/client.cpp b/src/realm/sync/client.cpp index aed5042813b..ec0e863fbd5 100644 --- a/src/realm/sync/client.cpp +++ b/src/realm/sync/client.cpp @@ -814,7 +814,7 @@ void SessionImpl::initiate_integrate_changesets(std::uint_fast64_t downloadable_ try { bool simulate_integration_error = (m_wrapper.m_simulate_integration_error && !changesets.empty()); if (simulate_integration_error) { - throw IntegrationException(ClientError::bad_changeset, "simulated failure"); + throw IntegrationException({ErrorCodes::BadChangeset, "simulated failure"}); } version_type client_version; if (REALM_LIKELY(!get_client().is_dry_run())) { @@ -905,8 +905,8 @@ bool SessionImpl::process_flx_bootstrap_message(const SyncProgress& progress, Do } catch (const LogicError& ex) { if (ex.code() == ErrorCodes::LimitExceeded) { - IntegrationException ex(ClientError::bad_changeset_size, - "bootstrap changeset too large to store in pending bootstrap store"); + IntegrationException ex( + {ErrorCodes::LimitExceeded, "bootstrap changeset too large to store in pending bootstrap store"}); on_integration_failure(ex); return true; } @@ -986,7 +986,7 @@ void SessionImpl::process_pending_flx_bootstrap() bool simulate_integration_error = (m_wrapper.m_simulate_integration_error && !pending_batch.changesets.empty()); if (simulate_integration_error) { - throw IntegrationException(ClientError::bad_changeset, "simulated failure"); + throw IntegrationException({ErrorCodes::BadChangeset, "simulated failure"}); } history.integrate_server_changesets( @@ -1089,12 +1089,11 @@ SyncClientHookAction SessionImpl::call_debug_hook(const SyncClientHookData& data auto action = m_wrapper.m_debug_hook(data); switch (action) { case realm::SyncClientHookAction::SuspendWithRetryableError: { - SessionErrorInfo err_info(make_error_code(ProtocolError::other_session_error), "hook requested error", - true); + SessionErrorInfo err_info(Status{ErrorCodes::RuntimeError, "hook requested error"}, true); err_info.server_requests_action = ProtocolErrorInfo::Action::Transient; auto err_processing_err = receive_error_message(err_info); - REALM_ASSERT_EX(!err_processing_err, err_processing_err.message()); + REALM_ASSERT_EX(err_processing_err.is_ok(), err_processing_err); return SyncClientHookAction::EarlyReturn; } case realm::SyncClientHookAction::TriggerReconnect: { diff --git a/src/realm/sync/client_base.hpp b/src/realm/sync/client_base.hpp index 08257e86d63..b190cd549fb 100644 --- a/src/realm/sync/client_base.hpp +++ b/src/realm/sync/client_base.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include namespace realm::sync { @@ -305,22 +306,23 @@ struct ClientConfig { /// /// \sa set_connection_state_change_listener(). struct SessionErrorInfo : public ProtocolErrorInfo { - SessionErrorInfo(const ProtocolErrorInfo& info, const std::error_code& ec) + SessionErrorInfo(const ProtocolErrorInfo& info) : ProtocolErrorInfo(info) - , error_code(ec) + , status(protocol_error_to_status(raw_error_code, message)) { } - SessionErrorInfo(const std::error_code& ec, bool try_again) - : ProtocolErrorInfo(ec.value(), ec.message(), try_again) - , error_code(ec) - { - } - SessionErrorInfo(const std::error_code& ec, const std::string& msg, bool try_again) - : ProtocolErrorInfo(ec.value(), msg, try_again) - , error_code(ec) + + SessionErrorInfo(Status status, bool try_again, ProtocolError protocol_error = ProtocolError::other_session_error) + : ProtocolErrorInfo(static_cast(protocol_error), status.reason(), try_again) + , status(std::move(status)) { } - std::error_code error_code; + + Status status; + + // If this error was created as a result of a network error, this will contain the closure code + // from the websocket close message. + websocket::WebSocketError websocket_error = websocket::WebSocketError::websocket_ok; }; enum class ConnectionState { disconnected, connecting, connected }; diff --git a/src/realm/sync/config.cpp b/src/realm/sync/config.cpp index dd57524a85f..b310014273e 100644 --- a/src/realm/sync/config.cpp +++ b/src/realm/sync/config.cpp @@ -25,49 +25,35 @@ #include namespace realm { +namespace { +constexpr static std::string_view s_middle_part(" Logs: "); +std::string format_sync_error_message(Status status, std::optional log_url) +{ + if (!log_url) { + return status.reason(); + } + + return util::format("%1%2%3", status.reason(), s_middle_part, *log_url); +} +} // namespace // sync defines its own copy of port_type to avoid depending on network.hpp, but they should be the same. static_assert(std::is_same_v); using ProtocolError = realm::sync::ProtocolError; -static const constexpr std::string_view s_middle(" Logs: "); - -SyncError::SyncError(std::error_code error_code, std::string_view msg, bool is_fatal, - std::optional serverLog, +SyncError::SyncError(Status status, bool is_fatal, std::optional server_log, std::vector compensating_writes) - : SystemError(error_code, serverLog ? util::format("%1%2%3", msg, s_middle, *serverLog) : std::string(msg)) + : RuntimeError(status.code(), format_sync_error_message(status, server_log)) , is_fatal(is_fatal) - , simple_message(std::string_view(what(), msg.size())) + , simple_message(std::string_view(reason()).substr(0, status.reason().size())) , compensating_writes_info(std::move(compensating_writes)) { - if (serverLog) { - logURL = std::string_view(what() + msg.size() + s_middle.size(), serverLog->size()); + if (server_log) { + logURL = std::string_view(reason()).substr(simple_message.size() + s_middle_part.size()); } } -bool SyncError::is_client_error() const -{ - return get_category() == realm::sync::client_error_category(); -} - -/// The error is a protocol error, which may either be connection-level or session-level. -bool SyncError::is_connection_level_protocol_error() const -{ - if (get_category() != realm::sync::protocol_error_category()) { - return false; - } - return !realm::sync::is_session_level_error(static_cast(get_system_error().value())); -} - -/// The error is a connection-level protocol error. -bool SyncError::is_session_level_protocol_error() const -{ - if (get_category() != realm::sync::protocol_error_category()) { - return false; - } - return realm::sync::is_session_level_error(static_cast(get_system_error().value())); -} /// The error indicates a client reset situation. bool SyncError::is_client_reset_requested() const @@ -76,7 +62,7 @@ bool SyncError::is_client_reset_requested() const server_requests_action == sync::ProtocolErrorInfo::Action::ClientResetNoRecovery) { return true; } - if (get_system_error() == make_error_code(sync::Client::Error::auto_client_reset_failure)) { + if (code() == ErrorCodes::AutoClientResetFailed) { return true; } return false; diff --git a/src/realm/sync/config.hpp b/src/realm/sync/config.hpp index 618c9d2716f..cd786150419 100644 --- a/src/realm/sync/config.hpp +++ b/src/realm/sync/config.hpp @@ -45,7 +45,7 @@ using port_type = std::uint_fast16_t; enum class ProtocolError; } // namespace sync -struct SyncError : public SystemError { +struct SyncError : public RuntimeError { enum class ClientResetModeAllowed { DoNotClientReset, RecoveryPermitted, RecoveryNotPermitted }; bool is_fatal; @@ -68,22 +68,12 @@ struct SyncError : public SystemError { // that caused a compensating write and why the write was illegal. std::vector compensating_writes_info; - SyncError(std::error_code error_code, std::string_view msg, bool is_fatal, - std::optional serverLog = std::nullopt, + SyncError(Status status, bool is_fatal, std::optional serverLog = std::nullopt, std::vector compensating_writes = {}); static constexpr const char c_original_file_path_key[] = "ORIGINAL_FILE_PATH"; static constexpr const char c_recovery_file_path_key[] = "RECOVERY_FILE_PATH"; - /// The error is a client error, which applies to the client and all its sessions. - bool is_client_error() const; - - /// The error is a protocol error, which may either be connection-level or session-level. - bool is_connection_level_protocol_error() const; - - /// The error is a connection-level protocol error. - bool is_session_level_protocol_error() const; - /// The error indicates a client reset situation. bool is_client_reset_requested() const; }; @@ -133,6 +123,7 @@ enum class SyncClientHookEvent { BootstrapMessageProcessed, BootstrapProcessed, ErrorMessageReceived, + BinaryMessageReceived, }; enum class SyncClientHookAction { @@ -140,6 +131,7 @@ enum class SyncClientHookAction { EarlyReturn, SuspendWithRetryableError, TriggerReconnect, + SimulateReadError, // Valid with BinaryMessageReceived }; inline std::ostream& operator<<(std::ostream& os, SyncClientHookAction action) diff --git a/src/realm/sync/network/default_socket.cpp b/src/realm/sync/network/default_socket.cpp index 9032bc3cd11..e7617afb671 100644 --- a/src/realm/sync/network/default_socket.cpp +++ b/src/realm/sync/network/default_socket.cpp @@ -78,15 +78,13 @@ class DefaultWebSocketImpl final : public DefaultWebSocket, public Config { { m_logger.error("Reading failed: %1", ec.message()); // Throws constexpr bool was_clean = false; - websocket_error_and_close_handler( - was_clean, Status{make_error_code(WebSocketError::websocket_read_error), ec.message()}); + websocket_error_and_close_handler(was_clean, WebSocketError::websocket_read_error, ec.message()); } void websocket_write_error_handler(std::error_code ec) override { m_logger.error("Writing failed: %1", ec.message()); // Throws constexpr bool was_clean = false; - websocket_error_and_close_handler( - was_clean, Status{make_error_code(WebSocketError::websocket_write_error), ec.message()}); + websocket_error_and_close_handler(was_clean, WebSocketError::websocket_write_error, ec.message()); } void websocket_handshake_error_handler(std::error_code ec, const HTTPHeaders*, const std::string_view* body) override @@ -144,30 +142,25 @@ class DefaultWebSocketImpl final : public DefaultWebSocket, public Config { } } - websocket_error_and_close_handler(was_clean, Status{make_error_code(error), ec.message()}); + websocket_error_and_close_handler(was_clean, error, ec.message()); } void websocket_protocol_error_handler(std::error_code ec) override { constexpr bool was_clean = false; - websocket_error_and_close_handler( - was_clean, Status{make_error_code(WebSocketError::websocket_protocol_error), ec.message()}); + websocket_error_and_close_handler(was_clean, WebSocketError::websocket_protocol_error, ec.message()); } - bool websocket_close_message_received(std::error_code ec, StringData message) override + bool websocket_close_message_received(WebSocketError code, std::string_view message) override { constexpr bool was_clean = true; - // Normal closure. - if (ec.value() == 1000) { - return websocket_error_and_close_handler(was_clean, Status::OK()); - } - return websocket_error_and_close_handler(was_clean, Status{ec, message}); + return websocket_error_and_close_handler(was_clean, code, message); } - bool websocket_error_and_close_handler(bool was_clean, Status status) + bool websocket_error_and_close_handler(bool was_clean, WebSocketError code, std::string_view reason) { if (!was_clean) { m_observer->websocket_error_handler(); } - return m_observer->websocket_closed_handler(was_clean, status); + return m_observer->websocket_closed_handler(was_clean, code, reason); } bool websocket_binary_message_received(const char* ptr, std::size_t size) override { @@ -275,8 +268,8 @@ void DefaultWebSocketImpl::handle_resolve(std::error_code ec, network::Endpoint: if (ec) { m_logger.error("Failed to resolve '%1:%2': %3", m_endpoint.address, m_endpoint.port, ec.message()); // Throws constexpr bool was_clean = false; - websocket_error_and_close_handler( - was_clean, Status{make_error_code(WebSocketError::websocket_resolve_failed), ec.message()}); // Throws + websocket_error_and_close_handler(was_clean, WebSocketError::websocket_resolve_failed, + ec.message()); // Throws return; } @@ -316,8 +309,8 @@ void DefaultWebSocketImpl::handle_tcp_connect(std::error_code ec, network::Endpo // All endpoints failed m_logger.error("Failed to connect to '%1:%2': All endpoints failed", m_endpoint.address, m_endpoint.port); constexpr bool was_clean = false; - websocket_error_and_close_handler( - was_clean, Status{make_error_code(WebSocketError::websocket_connection_failed), ec.message()}); // Throws + websocket_error_and_close_handler(was_clean, WebSocketError::websocket_connection_failed, + ec.message()); // Throws return; } @@ -357,18 +350,16 @@ void DefaultWebSocketImpl::initiate_http_tunnel() if (ec && ec != util::error::operation_aborted) { m_logger.error("Failed to establish HTTP tunnel: %1", ec.message()); constexpr bool was_clean = false; - websocket_error_and_close_handler( - was_clean, - Status{make_error_code(WebSocketError::websocket_connection_failed), ec.message()}); // Throws + websocket_error_and_close_handler(was_clean, WebSocketError::websocket_connection_failed, + ec.message()); // Throws return; } if (response.status != HTTPStatus::Ok) { m_logger.error("Proxy server returned response '%1 %2'", response.status, response.reason); // Throws constexpr bool was_clean = false; - websocket_error_and_close_handler( - was_clean, - Status{make_error_code(WebSocketError::websocket_connection_failed), response.reason}); // Throws + websocket_error_and_close_handler(was_clean, WebSocketError::websocket_connection_failed, + response.reason); // Throws return; } @@ -431,15 +422,15 @@ void DefaultWebSocketImpl::handle_ssl_handshake(std::error_code ec) if (ec) { REALM_ASSERT(ec != util::error::operation_aborted); constexpr bool was_clean = false; - std::error_code ec2; + WebSocketError parsed_error_code; if (ec == network::ssl::Errors::certificate_rejected) { - ec2 = make_error_code(WebSocketError::websocket_tls_handshake_failed); + parsed_error_code = WebSocketError::websocket_tls_handshake_failed; } else { - ec2 = make_error_code(WebSocketError::websocket_connection_failed); + parsed_error_code = WebSocketError::websocket_connection_failed; } - websocket_error_and_close_handler(was_clean, Status{ec2, ec.message()}); // Throws + websocket_error_and_close_handler(was_clean, parsed_error_code, ec.message()); // Throws return; } diff --git a/src/realm/sync/network/websocket.cpp b/src/realm/sync/network/websocket.cpp index e91d982598d..9baea8c4748 100644 --- a/src/realm/sync/network/websocket.cpp +++ b/src/realm/sync/network/websocket.cpp @@ -944,10 +944,10 @@ class WebSocket { return true; } - std::pair parse_close_message(const char* data, size_t size) + std::pair parse_close_message(const char* data, size_t size) { uint16_t error_code; - StringData error_message; + std::string_view error_message; if (size < 2) { // Error code 1005 is defined as // 1005 is a reserved value and MUST NOT be set as a status code in a @@ -962,11 +962,36 @@ class WebSocket { // network byte order. See https://tools.ietf.org/html/rfc6455#section-5.5.1 for more // details. error_code = ntohs((uint8_t(data[1]) << 8) | uint8_t(data[0])); - error_message = StringData(data + 2, size - 2); + error_message = std::string_view(data + 2, size - 2); } - std::error_code error_code_with_category{error_code, websocket::websocket_error_category()}; - return std::make_pair(error_code_with_category, error_message); + switch (static_cast(error_code)) { + case WebSocketError::websocket_ok: + case WebSocketError::websocket_going_away: + case WebSocketError::websocket_protocol_error: + case WebSocketError::websocket_unsupported_data: + case WebSocketError::websocket_reserved: + case WebSocketError::websocket_no_status_received: + case WebSocketError::websocket_abnormal_closure: + case WebSocketError::websocket_invalid_payload_data: + case WebSocketError::websocket_policy_violation: + case WebSocketError::websocket_message_too_big: + case WebSocketError::websocket_invalid_extension: + case WebSocketError::websocket_internal_server_error: + case WebSocketError::websocket_tls_handshake_failed: + + case WebSocketError::websocket_unauthorized: + case WebSocketError::websocket_forbidden: + case WebSocketError::websocket_moved_permanently: + case WebSocketError::websocket_client_too_old: + case WebSocketError::websocket_client_too_new: + case WebSocketError::websocket_protocol_mismatch: + break; + default: + error_code = 1008; + } + + return std::make_pair(static_cast(error_code), error_message); } // frame_reader_loop() uses the frame_reader to read and process the incoming @@ -1206,7 +1231,7 @@ bool websocket::Config::websocket_binary_message_received(const char*, size_t) return true; } -bool websocket::Config::websocket_close_message_received(std::error_code, StringData) +bool websocket::Config::websocket_close_message_received(WebSocketError, std::string_view) { return true; } diff --git a/src/realm/sync/network/websocket.hpp b/src/realm/sync/network/websocket.hpp index 1cb518410bf..25a422ca9e9 100644 --- a/src/realm/sync/network/websocket.hpp +++ b/src/realm/sync/network/websocket.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -76,7 +77,7 @@ class Config { /// websocket object is destroyed during execution of the function. virtual bool websocket_text_message_received(const char* data, size_t size); virtual bool websocket_binary_message_received(const char* data, size_t size); - virtual bool websocket_close_message_received(std::error_code error_code, StringData message); + virtual bool websocket_close_message_received(WebSocketError code, std::string_view message); virtual bool websocket_ping_message_received(const char* data, size_t size); virtual bool websocket_pong_message_received(const char* data, size_t size); //@} @@ -221,37 +222,6 @@ const std::error_category& http_error_category() noexcept; std::error_code make_error_code(HttpError) noexcept; -enum class WebSocketError { - websocket_ok = RLM_ERR_WEBSOCKET_OK, - websocket_going_away = RLM_ERR_WEBSOCKET_GOINGAWAY, - websocket_protocol_error = RLM_ERR_WEBSOCKET_PROTOCOLERROR, - websocket_unsupported_data = RLM_ERR_WEBSOCKET_UNSUPPORTEDDATA, - websocket_reserved = RLM_ERR_WEBSOCKET_RESERVED, - websocket_no_status_received = RLM_ERR_WEBSOCKET_NOSTATUSRECEIVED, - websocket_abnormal_closure = RLM_ERR_WEBSOCKET_ABNORMALCLOSURE, - websocket_invalid_payload_data = RLM_ERR_WEBSOCKET_INVALIDPAYLOADDATA, - websocket_policy_violation = RLM_ERR_WEBSOCKET_POLICYVIOLATION, - websocket_message_too_big = RLM_ERR_WEBSOCKET_MESSAGETOOBIG, - websocket_invalid_extension = RLM_ERR_WEBSOCKET_INAVALIDEXTENSION, - websocket_internal_server_error = RLM_ERR_WEBSOCKET_INTERNALSERVERERROR, - websocket_tls_handshake_failed = RLM_ERR_WEBSOCKET_TLSHANDSHAKEFAILED, // Used by default WebSocket - - // WebSocket Errors - reported by server - websocket_unauthorized = RLM_ERR_WEBSOCKET_UNAUTHORIZED, - websocket_forbidden = RLM_ERR_WEBSOCKET_FORBIDDEN, - websocket_moved_permanently = RLM_ERR_WEBSOCKET_MOVEDPERMANENTLY, - websocket_client_too_old = RLM_ERR_WEBSOCKET_CLIENT_TOO_OLD, - websocket_client_too_new = RLM_ERR_WEBSOCKET_CLIENT_TOO_NEW, - websocket_protocol_mismatch = RLM_ERR_WEBSOCKET_PROTOCOL_MISMATCH, - - websocket_resolve_failed = RLM_ERR_WEBSOCKET_RESOLVE_FAILED, - websocket_connection_failed = RLM_ERR_WEBSOCKET_CONNECTION_FAILED, - websocket_read_error = RLM_ERR_WEBSOCKET_READ_ERROR, - websocket_write_error = RLM_ERR_WEBSOCKET_WRITE_ERROR, - websocket_retry_error = RLM_ERR_WEBSOCKET_RETRY_ERROR, - websocket_fatal_error = RLM_ERR_WEBSOCKET_FATAL_ERROR, -}; - const std::error_category& websocket_error_category() noexcept; std::error_code make_error_code(WebSocketError) noexcept; diff --git a/src/realm/sync/noinst/client_history_impl.cpp b/src/realm/sync/noinst/client_history_impl.cpp index 7a95be436dd..d12b37b476e 100644 --- a/src/realm/sync/noinst/client_history_impl.cpp +++ b/src/realm/sync/noinst/client_history_impl.cpp @@ -404,8 +404,8 @@ void ClientHistory::integrate_server_changesets( } } catch (const BadChangesetError& e) { - throw IntegrationException(ClientError::bad_changeset, - util::format("Failed to parse received changeset: %1", e.what())); + throw IntegrationException( + {ErrorCodes::BadChangeset, util::format("Failed to parse received changeset: %1", e.what())}); } VersionID new_version{0, 0}; @@ -561,12 +561,12 @@ size_t ClientHistory::transform_and_apply_server_changesets(util::Span 0 && last_integrated_server_version < version_type(root.get_as_ref_or_tagged(s_progress_upload_server_version_iip).get_as_int())) { - throw IntegrationException(ClientError::bad_progress, - "last integrated server version of upload cursor cannot decrease"); + throw IntegrationException({ErrorCodes::SyncProtocolInvariantFailed, + "last integrated server version of upload cursor cannot decrease"}); } auto uploaded_bytes = std::uint_fast64_t(root.get_as_ref_or_tagged(s_progress_uploaded_bytes_iip).get_as_int()); diff --git a/src/realm/sync/noinst/client_history_impl.hpp b/src/realm/sync/noinst/client_history_impl.hpp index 16cac907fd1..a945954586d 100644 --- a/src/realm/sync/noinst/client_history_impl.hpp +++ b/src/realm/sync/noinst/client_history_impl.hpp @@ -67,21 +67,9 @@ constexpr int get_client_history_schema_version() noexcept return 12; } -class IntegrationException : public std::runtime_error { +class IntegrationException : public RuntimeError { public: - IntegrationException(ClientError code, const std::string& msg) - : std::runtime_error(msg) - , m_error(code) - { - } - - ClientError code() const noexcept - { - return m_error; - } - -private: - ClientError m_error; + using RuntimeError::RuntimeError; }; class ClientHistory final : public _impl::History, public TransformHistory { diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp index f90eeb0d499..cbbbf711545 100644 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ b/src/realm/sync/noinst/client_impl_base.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -466,13 +467,15 @@ void Connection::websocket_connected_handler(const std::string& protocol) } } } - logger.error("Bad protocol info from server: '%1'", protocol); // Throws + close_due_to_client_side_error({ErrorCodes::SyncProtocolNegotiationFailed, + util::format("Bad protocol info from server: '%1'", protocol)}, + IsFatal{true}, ConnectionTerminationReason::bad_headers_in_http_response); } else { - logger.error("Missing protocol info from server"); // Throws + close_due_to_client_side_error( + {ErrorCodes::SyncProtocolNegotiationFailed, "Missing protocol info from server"}, IsFatal{true}, + ConnectionTerminationReason::bad_headers_in_http_response); } - close_due_to_client_side_error(ClientError::bad_protocol_from_server, std::nullopt, IsFatal{true}, - ConnectionTerminationReason::bad_headers_in_http_response); // Throws } @@ -482,10 +485,11 @@ bool Connection::websocket_binary_message_received(util::Span data) logger.debug("Received binary message after connection was force closed"); return false; } - std::error_code ec; + + if ( using sf = SimulatedFailure; - if (sf::trigger(sf::sync_client__read_head, ec)) { - read_or_write_error(ec, "simulated read error"); + if (sf::check_trigger(sf::sync_client__read_head)) { + read_or_write_error(sf::sync_server__read_head, "simulated read error"); return bool(m_websocket); } @@ -499,30 +503,30 @@ void Connection::websocket_error_handler() m_websocket_error_received = true; } -bool Connection::websocket_closed_handler(bool was_clean, Status status) +bool Connection::websocket_closed_handler(bool was_clean, WebSocketError code, std::string_view message) { if (m_force_closed) { logger.debug("Received websocket close message after connection was force closed"); return false; } - logger.info("Closing the websocket with status='%1', was_clean='%2'", status, was_clean); - auto error_code = status.get_std_error_code(); + logger.info("Closing the websocket with code='%1', was_clean='%2'", code, was_clean); - switch (static_cast(error_code.value())) { + switch (code) { case WebSocketError::websocket_ok: break; case WebSocketError::websocket_resolve_failed: [[fallthrough]]; case WebSocketError::websocket_connection_failed: { constexpr bool try_again = true; - involuntary_disconnect(SessionErrorInfo{error_code, status.reason(), try_again}, + involuntary_disconnect(SessionErrorInfo{{ErrorCodes::SyncConnectFailed, message}, try_again}, ConnectionTerminationReason::connect_operation_failed); // Throws break; } case WebSocketError::websocket_read_error: [[fallthrough]]; case WebSocketError::websocket_write_error: { - read_or_write_error(error_code, status.reason()); // Throws + close_due_to_network_error({ErrorCodes::ConnectionClosed, message}, IsFatal{false}, + ConnectionTerminationReason::read_or_write_error, code); break; } case WebSocketError::websocket_going_away: @@ -540,55 +544,45 @@ bool Connection::websocket_closed_handler(bool was_clean, Status status) case WebSocketError::websocket_no_status_received: [[fallthrough]]; case WebSocketError::websocket_invalid_extension: { - constexpr bool try_again = true; - SessionErrorInfo error_info{error_code, status.reason(), try_again}; - involuntary_disconnect(std::move(error_info), - ConnectionTerminationReason::websocket_protocol_violation); // Throws + close_due_to_network_error({ErrorCodes::SyncProtocolInvariantFailed, message}, IsFatal{false}, + ConnectionTerminationReason::websocket_protocol_violation, code); break; } case WebSocketError::websocket_message_too_big: { constexpr bool try_again = true; - auto ec = make_error_code(ProtocolError::limits_exceeded); - auto message = - util::format("Sync websocket closed because the server received a message that was too large: %1", - status.reason()); - SessionErrorInfo error_info(ec, message, try_again); + auto pretty_message = util::format( + "Sync websocket closed because the server received a message that was too large: %1", message); + SessionErrorInfo error_info(Status{ErrorCodes::LimitExceeded, std::move(pretty_message)}, try_again); error_info.server_requests_action = ProtocolErrorInfo::Action::ClientReset; + error_info.websocket_error = code; involuntary_disconnect(std::move(error_info), ConnectionTerminationReason::websocket_protocol_violation); // Throws break; } case WebSocketError::websocket_tls_handshake_failed: { - error_code = ClientError::ssl_server_cert_rejected; - close_due_to_client_side_error(error_code, status.reason(), IsFatal{false}, - ConnectionTerminationReason::ssl_certificate_rejected); // Throws + close_due_to_network_error({ErrorCodes::TlsHandshakeFailed, message}, IsFatal{false}, + ConnectionTerminationReason::ssl_certificate_rejected, code); // Throws break; } case WebSocketError::websocket_client_too_old: { - error_code = ClientError::client_too_old_for_server; - close_due_to_client_side_error(error_code, status.reason(), IsFatal{true}, - ConnectionTerminationReason::http_response_says_fatal_error); // Throws + close_due_to_network_error({ErrorCodes::SyncProtocolNegotiationFailed, message}, IsFatal{true}, + ConnectionTerminationReason::http_response_says_fatal_error, code); // Throws break; } case WebSocketError::websocket_client_too_new: { - error_code = ClientError::client_too_new_for_server; - close_due_to_client_side_error(error_code, status.reason(), IsFatal{true}, - ConnectionTerminationReason::http_response_says_fatal_error); // Throws + close_due_to_network_error({ErrorCodes::SyncProtocolNegotiationFailed, message}, IsFatal{true}, + ConnectionTerminationReason::http_response_says_fatal_error, code); // Throws break; } case WebSocketError::websocket_protocol_mismatch: { - error_code = ClientError::protocol_mismatch; - close_due_to_client_side_error(error_code, status.reason(), IsFatal{true}, - ConnectionTerminationReason::http_response_says_fatal_error); // Throws + close_due_to_network_error({ErrorCodes::SyncProtocolNegotiationFailed, message}, IsFatal{true}, + ConnectionTerminationReason::http_response_says_fatal_error, code); // Throws break; } case WebSocketError::websocket_fatal_error: [[fallthrough]]; - case WebSocketError::websocket_forbidden: { - close_due_to_client_side_error(error_code, status.reason(), IsFatal{true}, - ConnectionTerminationReason::http_response_says_fatal_error); // Throws - break; - } + case WebSocketError::websocket_forbidden: + [[fallthrough]]; case WebSocketError::websocket_unauthorized: [[fallthrough]]; case WebSocketError::websocket_moved_permanently: @@ -598,8 +592,9 @@ bool Connection::websocket_closed_handler(bool was_clean, Status status) case WebSocketError::websocket_abnormal_closure: [[fallthrough]]; case WebSocketError::websocket_retry_error: { - close_due_to_client_side_error(error_code, status.reason(), IsFatal{false}, - ConnectionTerminationReason::http_response_says_nonfatal_error); // Throws + close_due_to_network_error({ErrorCodes::ConnectionClosed, message}, IsFatal{false}, + ConnectionTerminationReason::http_response_says_nonfatal_error, + code); // Throws break; } } @@ -700,13 +695,13 @@ struct Connection::WebSocketObserverShim : public sync::WebSocketObserver { return conn->websocket_binary_message_received(data); } - bool websocket_closed_handler(bool was_clean, Status status) override + bool websocket_closed_handler(bool was_clean, websocket::WebSocketError code, std::string_view message) override { if (sentinel->destroyed) { return true; } - return conn->websocket_closed_handler(was_clean, std::move(status)); + return conn->websocket_closed_handler(was_clean, code, message); } }; @@ -788,7 +783,8 @@ void Connection::handle_connect_wait(Status status) REALM_ASSERT_EX(m_state == ConnectionState::connecting, m_state); logger.info("Connect timeout"); // Throws constexpr bool try_again = true; - involuntary_disconnect(SessionErrorInfo{ClientError::connect_timeout, try_again}, + + involuntary_disconnect(SessionErrorInfo{status, try_again, ProtocolError::other_error}, ConnectionTerminationReason::sync_connect_timeout); // Throws } @@ -926,9 +922,8 @@ void Connection::initiate_pong_timeout() void Connection::handle_pong_timeout() { REALM_ASSERT(m_waiting_for_pong); - logger.debug("Timeout on reception of PONG message"); // Throws - close_due_to_client_side_error(ClientError::pong_timeout, std::nullopt, IsFatal{false}, - ConnectionTerminationReason::pong_timeout); + close_due_to_client_side_error({ErrorCodes::ConnectionClosed, "Timed out waiting for PONG message"}, + IsFatal{false}, ConnectionTerminationReason::pong_timeout); } @@ -1093,34 +1088,35 @@ void Connection::handle_disconnect_wait(Status status) } -void Connection::read_or_write_error(std::error_code ec, std::string_view msg) +void Connection::read_or_write_error(std::error_code, std::string_view msg) { - close_due_to_client_side_error(ec, msg, IsFatal{false}, + close_due_to_client_side_error({ErrorCodes::ConnectionClosed, msg}, IsFatal{false}, ConnectionTerminationReason::read_or_write_error); // Throws } -void Connection::close_due_to_protocol_error(std::error_code ec, std::optional msg) +void Connection::close_due_to_protocol_error(Status status) { - close_due_to_client_side_error(ec, msg, IsFatal{true}, + close_due_to_client_side_error(std::move(status), IsFatal{true}, ConnectionTerminationReason::sync_protocol_violation); // Throws } - -// Close connection due to error discovered on the client-side. -void Connection::close_due_to_client_side_error(std::error_code ec, std::optional msg, - IsFatal is_fatal, ConnectionTerminationReason reason) +void Connection::close_due_to_network_error(Status status, IsFatal is_fatal, ConnectionTerminationReason reason, + WebSocketError websocket_error) { - logger.info("Connection closed due to error"); // Throws + logger.info("Connection closed due to error: %1", status); // Throws const bool try_again = !is_fatal; - std::string message = ec.message(); - if (msg) { - message += ": "; - message += *msg; - } - involuntary_disconnect(SessionErrorInfo{ec, message, try_again}, reason); // Throws + SessionErrorInfo error_info(std::move(status), try_again); + error_info.websocket_error = websocket_error; + involuntary_disconnect(std::move(error_info), reason); // } +void Connection::close_due_to_client_side_error(Status status, IsFatal is_fatal, ConnectionTerminationReason reason) +{ + logger.info("Connection closed due to error: %1", status); // Throws + const bool try_again = !is_fatal; + involuntary_disconnect(SessionErrorInfo{std::move(status), try_again}, reason); // Throws +} // Close connection due to error discovered on the server-side, and then // reported to the client by way of a connection-level ERROR message. @@ -1129,10 +1125,9 @@ void Connection::close_due_to_server_side_error(ProtocolError error_code, const logger.info("Connection closed due to error reported by server: %1 (%2)", info.message, int(error_code)); // Throws - std::error_code ec = make_error_code(error_code); const auto reason = info.try_again ? ConnectionTerminationReason::server_said_try_again_later : ConnectionTerminationReason::server_said_do_not_reconnect; - involuntary_disconnect(SessionErrorInfo{info, ec}, reason); // Throws + involuntary_disconnect(SessionErrorInfo{info}, reason); // Throws } @@ -1195,16 +1190,14 @@ void Connection::receive_pong(milliseconds_type timestamp) bool legal_at_this_time = (m_waiting_for_pong && !m_send_ping); if (REALM_UNLIKELY(!legal_at_this_time)) { - logger.error("Illegal message at this time"); - std::error_code ec = ClientError::bad_message_order; - close_due_to_protocol_error(ec); // Throws + close_due_to_protocol_error( + {ErrorCodes::SyncProtocolInvariantFailed, "Illegal message at this time"}); // Throws return; } if (REALM_UNLIKELY(timestamp != m_last_ping_sent_at)) { - logger.error("Bad timestamp in PONG message"); - std::error_code ec = ClientError::bad_timestamp; - close_due_to_protocol_error(ec); // Throws + close_due_to_protocol_error( + {ErrorCodes::SyncProtocolInvariantFailed, "Bad timestamp in PONG message"}); // Throws return; } @@ -1243,8 +1236,9 @@ Session* Connection::find_and_validate_session(session_ident_type session_ident, } // Check the history to see if the message received was for a previous session if (auto it = m_session_history.find(session_ident); it == m_session_history.end()) { - logger.error("Bad session identifier in %1 message, session_ident = %2", message, session_ident); - close_due_to_protocol_error(ClientError::bad_session_ident); // Throws + close_due_to_protocol_error( + {ErrorCodes::SyncProtocolInvariantFailed, + util::format("Bad session identifier in %1 message, session_ident = %2", message, session_ident)}); } else { logger.error("Received %1 message for closed session, session_ident = %2", message, @@ -1261,9 +1255,9 @@ void Connection::receive_error_message(const ProtocolErrorInfo& info, session_id if (REALM_UNLIKELY(!sess)) { return; } - std::error_code ec = sess->receive_error_message(info); // Throws - if (ec) { - close_due_to_protocol_error(ec); // Throws + auto status = sess->receive_error_message(info); // Throws + if (!status.is_ok()) { + close_due_to_protocol_error(std::move(status)); // Throws return; } @@ -1278,18 +1272,19 @@ void Connection::receive_error_message(const ProtocolErrorInfo& info, session_id info.server_requests_action); // Throws bool known_error_code = bool(get_protocol_error_message(info.raw_error_code)); + std::string_view err_msg; if (REALM_LIKELY(known_error_code)) { ProtocolError error_code = ProtocolError(info.raw_error_code); if (REALM_LIKELY(!is_session_level_error(error_code))) { close_due_to_server_side_error(error_code, info); // Throws return; } - logger.error("Not a connection-level error code"); // Throws + err_msg = "Not a connection-level error code"; // Throws } else { - logger.error("Unknown error code"); // Throws + err_msg = "Unknown error code"; // Throws } - close_due_to_protocol_error(ClientError::bad_error_code); // Throws + close_due_to_protocol_error({ErrorCodes::SyncProtocolInvariantFailed, err_msg}); // Throws } @@ -1297,13 +1292,13 @@ void Connection::receive_query_error_message(int raw_error_code, std::string_vie session_ident_type session_ident) { if (session_ident == 0) { - logger.error("Received query error message for session ident 0."); // throws; - return close_due_to_protocol_error(ClientError::bad_session_ident); + return close_due_to_protocol_error( + {ErrorCodes::SyncProtocolInvariantFailed, "Received query error message for session ident 0"}); } if (!is_flx_sync_connection()) { - logger.error("Received query error message on a non-FLX sync connection"); - return close_due_to_protocol_error(ClientError::bad_protocol_from_server); + return close_due_to_protocol_error( + {ErrorCodes::SyncProtocolInvariantFailed, "Received query error message on a non-FLX sync connection"}); } Session* sess = find_and_validate_session(session_ident, "QUERY_ERROR"); @@ -1311,8 +1306,8 @@ void Connection::receive_query_error_message(int raw_error_code, std::string_vie return; } - if (auto ec = sess->receive_query_error_message(raw_error_code, message, query_version)) { - close_due_to_protocol_error(ec); + if (auto status = sess->receive_query_error_message(raw_error_code, message, query_version); !status.is_ok()) { + close_due_to_protocol_error(std::move(status)); } } @@ -1324,9 +1319,9 @@ void Connection::receive_ident_message(session_ident_type session_ident, SaltedF return; } - std::error_code ec = sess->receive_ident_message(client_file_ident); // Throws - if (ec) - close_due_to_protocol_error(ec); // Throws + auto status = sess->receive_ident_message(client_file_ident); // Throws + if (!status.is_ok()) + close_due_to_protocol_error(std::move(status)); // Throws } void Connection::receive_download_message(session_ident_type session_ident, const SyncProgress& progress, @@ -1350,9 +1345,9 @@ void Connection::receive_mark_message(session_ident_type session_ident, request_ return; } - std::error_code ec = sess->receive_mark_message(request_ident); // Throws - if (ec) - close_due_to_protocol_error(ec); // Throws + auto status = sess->receive_mark_message(request_ident); // Throws + if (!status.is_ok()) + close_due_to_protocol_error(std::move(status)); // Throws } @@ -1363,9 +1358,9 @@ void Connection::receive_unbound_message(session_ident_type session_ident) return; } - std::error_code ec = sess->receive_unbound_message(); // Throws - if (ec) { - close_due_to_protocol_error(ec); // Throws + auto status = sess->receive_unbound_message(); // Throws + if (!status.is_ok()) { + close_due_to_protocol_error(std::move(status)); // Throws return; } @@ -1383,39 +1378,8 @@ void Connection::receive_test_command_response(session_ident_type session_ident, return; } - if (auto ec = sess->receive_test_command_response(request_ident, body)) { - close_due_to_protocol_error(ec); - } -} - - -void Connection::handle_protocol_error(ClientProtocol::Error error) -{ - switch (error) { - case ClientProtocol::Error::unknown_message: - close_due_to_protocol_error(ClientError::unknown_message); // Throws - break; - case ClientProtocol::Error::bad_syntax: - close_due_to_protocol_error(ClientError::bad_syntax); // Throws - break; - case ClientProtocol::Error::limits_exceeded: - close_due_to_protocol_error(ClientError::limits_exceeded); // Throws - break; - case ClientProtocol::Error::bad_decompression: - close_due_to_protocol_error(ClientError::bad_compression); // Throws - break; - case ClientProtocol::Error::bad_changeset_header_syntax: - close_due_to_protocol_error(ClientError::bad_changeset_header_syntax); // Throws - break; - case ClientProtocol::Error::bad_changeset_size: - close_due_to_protocol_error(ClientError::bad_changeset_size); // Throws - break; - case ClientProtocol::Error::bad_server_version: - close_due_to_protocol_error(ClientError::bad_server_version); // Throws - break; - case ClientProtocol::Error::bad_error_code: - close_due_to_protocol_error(ClientError::bad_error_code); // Throws - break; + if (auto status = sess->receive_test_command_response(request_ident, body); !status.is_ok()) { + close_due_to_protocol_error(std::move(status)); } } @@ -1500,7 +1464,7 @@ void Session::integrate_changesets(ClientReplication& repl, const SyncProgress& auto& history = repl.get_history(); if (received_changesets.empty()) { if (download_batch_state == DownloadBatchState::MoreToCome) { - throw IntegrationException(ClientError::bad_progress, + throw IntegrationException(ErrorCodes::SyncProtocolInvariantFailed, "received empty download message that was not the last in batch"); } history.set_sync_progress(progress, &downloadable_bytes, version_info); // Throws @@ -1529,9 +1493,7 @@ void Session::integrate_changesets(ClientReplication& repl, const SyncProgress& pending_error.compensating_write_rejected_client_version, pending_error.compensating_write_server_version, pending_error.message); try { - ProtocolError error_code = ProtocolError(pending_error.raw_error_code); - on_connection_state_changed(m_conn.get_state(), - SessionErrorInfo{pending_error, make_error_code(error_code)}); + on_connection_state_changed(m_conn.get_state(), SessionErrorInfo{pending_error}); } catch (...) { logger.error("Exception thrown while reporting compensating write: %1", exception_to_status()); @@ -1550,10 +1512,8 @@ void Session::on_integration_failure(const IntegrationException& error) m_error_to_send = true; constexpr bool try_again = true; - std::error_code error_code = error.code(); - auto msg = error_code.message() + ": " + error.what(); // Surface the error to the user otherwise is lost. - on_connection_state_changed(m_conn.get_state(), SessionErrorInfo{error.code(), std::move(msg), try_again}); + on_connection_state_changed(m_conn.get_state(), SessionErrorInfo{error.to_status(), try_again}); // Since the deactivation process has not been initiated, the UNBIND // message cannot have been sent unless an ERROR message was received. @@ -1583,7 +1543,8 @@ void Session::on_changesets_integrated(version_type client_version, const SyncPr do_recognize_sync_version(client_version); // Allows upload process to resume check_for_download_completion(); // Throws - // If the client migrated from PBS to FLX, create subscriptions when new tables are received from server. + // If the client migrated from PBS to FLX, create subscriptions when new tables are received from + // server. if (auto migration_store = get_migration_store(); migration_store && m_is_flx_sync_session) { auto& flx_subscription_store = *get_flx_subscription_store(); get_migration_store()->create_subscriptions(flx_subscription_store); @@ -1626,9 +1587,9 @@ void Session::activate() // DB. If it did, then the fresh DB would stay alive for the duration of this sync session // and we want to clean it up once the reset is finished. Additionally, the fresh copy will // be set to a new copy on every reset so there is no reason to keep a reference to it. - // The modification to the client reset config happens via std::move(client_reset_config->fresh_copy). - // If the client reset config were a `const &` then this std::move would create another strong - // reference which we don't want to happen. + // The modification to the client reset config happens via + // std::move(client_reset_config->fresh_copy). If the client reset config were a `const &` then + // this std::move would create another strong reference which we don't want to happen. util::Optional& client_reset_config = get_client_reset_config(); bool file_exists = util::File::exists(get_realm_path()); @@ -1677,7 +1638,7 @@ void Session::activate() logger.error("Error integrating bootstrap changesets: %1", error.what()); m_suspended = true; m_conn.one_less_active_unsuspended_session(); // Throws - on_suspended(SessionErrorInfo{error.code(), false}); + on_suspended(SessionErrorInfo{error.to_status(), false}); } if (has_pending_client_reset) { @@ -1866,19 +1827,21 @@ void Session::send_bind_message() if (!bind_json_data.empty()) { json_data_dump = bind_json_data.dump(); } - logger.debug( - "Sending: BIND(session_ident=%1, need_client_file_ident=%2, is_subserver=%3, json_data=\"%4\")", - session_ident, need_client_file_ident, is_subserver, json_data_dump); + logger.debug("Sending: BIND(session_ident=%1, need_client_file_ident=%2, is_subserver=%3, " + "json_data=\"%4\")", + session_ident, need_client_file_ident, is_subserver, json_data_dump); } protocol.make_flx_bind_message(protocol_version, out, session_ident, bind_json_data, empty_access_token, - need_client_file_ident, is_subserver); // Throws + need_client_file_ident, + is_subserver); // Throws } else { std::string server_path = get_virt_path(); logger.debug("Sending: BIND(session_ident=%1, need_client_file_ident=%2, is_subserver=%3, server_path=%4)", session_ident, need_client_file_ident, is_subserver, server_path); protocol.make_pbs_bind_message(protocol_version, out, session_ident, server_path, empty_access_token, - need_client_file_ident, is_subserver); // Throws + need_client_file_ident, + is_subserver); // Throws } m_conn.initiate_write_message(out, this); // Throws @@ -2140,8 +2103,7 @@ void Session::send_json_error_message() ClientProtocol& protocol = m_conn.get_client_protocol(); OutputBuffer& out = m_conn.get_output_buffer(); session_ident_type session_ident = get_ident(); - auto client_error = m_client_error->code(); - auto protocol_error = client_error_to_protocol_error(client_error); + auto protocol_error = ErrorCodes::RuntimeError; auto message = m_client_error->what(); logger.info("Sending: ERROR \"%1\" (error_code=%2, session_ident=%3)", message, static_cast(protocol_error), @@ -2182,7 +2144,7 @@ void Session::send_test_command_message() } -std::error_code Session::receive_ident_message(SaltedFileIdent client_file_ident) +Status Session::receive_ident_message(SaltedFileIdent client_file_ident) { logger.debug("Received: IDENT(client_file_ident=%1, client_file_ident_salt=%2)", client_file_ident.ident, client_file_ident.salt); // Throws @@ -2191,21 +2153,18 @@ std::error_code Session::receive_ident_message(SaltedFileIdent client_file_ident // because in that case, the associated Realm and SessionWrapper must // not be accessed any longer. if (m_state != Active) - return std::error_code{}; // Success + return Status::OK(); // Success 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)) { - logger.error("Illegal message at this time"); - return ClientError::bad_message_order; + return {ErrorCodes::SyncProtocolInvariantFailed, "IDENT message is not legal at this time"}; } if (REALM_UNLIKELY(client_file_ident.ident < 1)) { - logger.error("Bad client file identifier in IDENT message"); - return ClientError::bad_client_file_ident; + return {ErrorCodes::SyncProtocolInvariantFailed, "Bad client file identifier in IDENT message"}; } if (REALM_UNLIKELY(client_file_ident.salt == 0)) { - logger.error("Bad client file identifier salt in IDENT message"); - return ClientError::bad_client_file_ident_salt; + return {ErrorCodes::SyncProtocolInvariantFailed, "Bad client file identifier salt in IDENT message"}; } m_client_file_ident = client_file_ident; @@ -2213,7 +2172,7 @@ std::error_code Session::receive_ident_message(SaltedFileIdent client_file_ident if (REALM_UNLIKELY(get_client().is_dry_run())) { // Ready to send the IDENT message ensure_enlisted_to_send(); // Throws - return std::error_code{}; // Success + return Status::OK(); } // access before the client reset (if applicable) because @@ -2289,9 +2248,9 @@ std::error_code Session::receive_ident_message(SaltedFileIdent client_file_ident catch (const std::exception& e) { auto err_msg = util::format("A fatal error occured during client reset: '%1'", e.what()); logger.error(err_msg.c_str()); - SessionErrorInfo err_info(make_error_code(ClientError::auto_client_reset_failure), err_msg, false); + SessionErrorInfo err_info(Status{ErrorCodes::AutoClientResetFailed, std::move(err_msg)}, false); suspend(err_info); - return {}; + return Status::OK(); } if (!did_client_reset) { repl.get_history().set_client_file_ident(client_file_ident, m_fix_up_object_ids); // Throws @@ -2302,7 +2261,7 @@ std::error_code Session::receive_ident_message(SaltedFileIdent client_file_ident // Ready to send the IDENT message ensure_enlisted_to_send(); // Throws - return std::error_code{}; // Success + return Status::OK(); // Success } void Session::receive_download_message(const SyncProgress& progress, std::uint_fast64_t downloadable_bytes, @@ -2326,10 +2285,11 @@ void Session::receive_download_message(const SyncProgress& progress, std::uint_f progress.download.server_version, progress.download.last_integrated_client_version, progress.latest_server_version.version, progress.latest_server_version.salt, progress.upload.client_version, progress.upload.last_integrated_server_version, downloadable_bytes, - batch_state != DownloadBatchState::MoreToCome, query_version, received_changesets.size()); // Throws + batch_state != DownloadBatchState::MoreToCome, query_version, + received_changesets.size()); // Throws - // Ignore download messages when the client detects an error. This is to prevent transforming the same bad - // changeset over and over again. + // Ignore download messages when the client detects an error. This is to prevent transforming the same + // bad changeset over and over again. if (m_client_error) { logger.debug("Ignoring download message because the client detected an integration error"); return; @@ -2337,28 +2297,26 @@ void Session::receive_download_message(const SyncProgress& progress, std::uint_f bool legal_at_this_time = (m_ident_message_sent && !m_error_message_received && !m_unbound_message_received); if (REALM_UNLIKELY(!legal_at_this_time)) { - logger.error("Illegal message at this time"); - m_conn.close_due_to_protocol_error(ClientError::bad_message_order); + m_conn.close_due_to_protocol_error( + {ErrorCodes::SyncProtocolInvariantFailed, "DOWNLOAD messages are illegal at this time"}); return; } - int error_code = 0; - if (REALM_UNLIKELY(!check_received_sync_progress(progress, error_code))) { - logger.error("Bad sync progress received (%1)", error_code); - m_conn.close_due_to_protocol_error(ClientError::bad_progress); + if (auto okay_progress = check_received_sync_progress(progress); REALM_UNLIKELY(!okay_progress.is_ok())) { + m_conn.close_due_to_protocol_error(okay_progress); return; } version_type server_version = m_progress.download.server_version; version_type last_integrated_client_version = m_progress.download.last_integrated_client_version; for (const Transformer::RemoteChangeset& changeset : received_changesets) { - // Check that per-changeset server version is strictly increasing, except in FLX sync where the server version - // must be increasing, but can stay the same during bootstraps. + // Check that per-changeset server version is strictly increasing, except in FLX sync where the + // server version must be increasing, but can stay the same during bootstraps. bool good_server_version = m_is_flx_sync_session ? (changeset.remote_version >= server_version) : (changeset.remote_version > server_version); if (!good_server_version) { - logger.error("Bad server version in changeset header (DOWNLOAD) (%1, %2, %3)", changeset.remote_version, - server_version, progress.download.server_version); - m_conn.close_due_to_protocol_error(ClientError::bad_server_version); + auto msg = util::format("Bad server version in changeset header (DOWNLOAD) (%1, %2, %3)", + changeset.remote_version, server_version, progress.download.server_version); + m_conn.close_due_to_protocol_error({ErrorCodes::SyncProtocolInvariantFailed, std::move(msg)}); return; } server_version = changeset.remote_version; @@ -2368,11 +2326,11 @@ void Session::receive_download_message(const SyncProgress& progress, std::uint_f (changeset.last_integrated_local_version >= last_integrated_client_version && changeset.last_integrated_local_version <= progress.download.last_integrated_client_version); if (!good_client_version) { - logger.error("Bad last integrated client version in changeset header (DOWNLOAD) " - "(%1, %2, %3)", - changeset.last_integrated_local_version, last_integrated_client_version, - progress.download.last_integrated_client_version); - m_conn.close_due_to_protocol_error(ClientError::bad_client_version); + auto msg = util::format("Bad last integrated client version in changeset header (DOWNLOAD) " + "(%1, %2, %3)", + changeset.last_integrated_local_version, last_integrated_client_version, + progress.download.last_integrated_client_version); + m_conn.close_due_to_protocol_error({ErrorCodes::SyncProtocolInvariantFailed, std::move(msg)}); return; } last_integrated_client_version = changeset.last_integrated_local_version; @@ -2381,8 +2339,10 @@ void Session::receive_download_message(const SyncProgress& progress, std::uint_f bool good_file_ident = (changeset.origin_file_ident > 0 && changeset.origin_file_ident != m_client_file_ident.ident); if (!good_file_ident) { - logger.error("Bad origin file identifier"); - m_conn.close_due_to_protocol_error(ClientError::bad_origin_file_ident); + m_conn.close_due_to_protocol_error( + {ErrorCodes::SyncProtocolInvariantFailed, + util::format("Bad origin file identifier in downloaded changeset header: %1", + changeset.origin_file_ident)}); return; } } @@ -2399,7 +2359,8 @@ void Session::receive_download_message(const SyncProgress& progress, std::uint_f return; } - initiate_integrate_changesets(downloadable_bytes, batch_state, progress, received_changesets); // Throws + initiate_integrate_changesets(downloadable_bytes, batch_state, progress, + received_changesets); // Throws hook_action = call_debug_hook(SyncClientHookEvent::DownloadMessageIntegrated, progress, query_version, batch_state, received_changesets.size()); @@ -2408,12 +2369,12 @@ void Session::receive_download_message(const SyncProgress& progress, std::uint_f } REALM_ASSERT_EX(hook_action == SyncClientHookAction::NoAction, hook_action); - // When we receive a DOWNLOAD message successfully, we can clear the backoff timer value used to reconnect - // after a retryable session error. + // When we receive a DOWNLOAD message successfully, we can clear the backoff timer value used to + // reconnect after a retryable session error. clear_resumption_delay_state(); } -std::error_code Session::receive_mark_message(request_ident_type request_ident) +Status Session::receive_mark_message(request_ident_type request_ident) { logger.debug("Received: MARK(request_ident=%1)", request_ident); // Throws @@ -2421,38 +2382,35 @@ std::error_code Session::receive_mark_message(request_ident_type request_ident) // because in that case, the associated Realm and SessionWrapper must // not be accessed any longer. if (m_state != Active) - return std::error_code{}; // Success + return Status::OK(); // Success bool legal_at_this_time = (m_ident_message_sent && !m_error_message_received && !m_unbound_message_received); if (REALM_UNLIKELY(!legal_at_this_time)) { - logger.error("Illegal message at this time"); - return ClientError::bad_message_order; + return {ErrorCodes::SyncProtocolInvariantFailed, "MARK message is not legal at this time"}; } bool good_request_ident = (request_ident <= m_last_download_mark_sent && request_ident > m_last_download_mark_received); if (REALM_UNLIKELY(!good_request_ident)) { - logger.error("Bad request identifier in MARK message"); - return ClientError::bad_request_ident; + return {ErrorCodes::SyncProtocolInvariantFailed, "Bad request identifier in MARK message"}; } m_server_version_at_last_download_mark = m_progress.download.server_version; m_last_download_mark_received = request_ident; check_for_download_completion(); // Throws - return std::error_code{}; // Success + return Status::OK(); } // The caller (Connection) must discard the session if the session has become // deactivated upon return. -std::error_code Session::receive_unbound_message() +Status Session::receive_unbound_message() { logger.debug("Received: UNBOUND"); bool legal_at_this_time = (m_unbind_message_sent && !m_error_message_received && !m_unbound_message_received); if (REALM_UNLIKELY(!legal_at_this_time)) { - logger.error("Illegal message at this time"); - return ClientError::bad_message_order; + return {ErrorCodes::SyncProtocolInvariantFailed, "UNBOUND message is not legal at this time"}; } // The fact that the UNBIND message has been sent, but an ERROR message has @@ -2471,11 +2429,11 @@ std::error_code Session::receive_unbound_message() // Life cycle state is now Deactivated } - return std::error_code{}; // Success + return Status::OK(); // Success } -std::error_code Session::receive_query_error_message(int error_code, std::string_view message, int64_t query_version) +Status Session::receive_query_error_message(int error_code, std::string_view message, int64_t query_version) { logger.info("Received QUERY_ERROR \"%1\" (error_code=%2, query_version=%3)", message, error_code, query_version); // Ignore the message if the deactivation process has been initiated, @@ -2484,31 +2442,30 @@ std::error_code Session::receive_query_error_message(int error_code, std::string if (m_state == Active) { on_flx_sync_error(query_version, std::string_view(message.data(), message.size())); // throws } - return {}; + return Status::OK(); } // The caller (Connection) must discard the session if the session has become // deactivated upon return. -std::error_code Session::receive_error_message(const ProtocolErrorInfo& info) +Status Session::receive_error_message(const ProtocolErrorInfo& info) { logger.info("Received: ERROR \"%1\" (error_code=%2, try_again=%3, error_action=%4)", info.message, info.raw_error_code, info.try_again, info.server_requests_action); // Throws bool legal_at_this_time = (m_bind_message_sent && !m_error_message_received && !m_unbound_message_received); if (REALM_UNLIKELY(!legal_at_this_time)) { - logger.error("Illegal message at this time"); - return ClientError::bad_message_order; + return {ErrorCodes::SyncProtocolInvariantFailed, "ERROR message is illegal at this time"}; } bool known_error_code = bool(get_protocol_error_message(info.raw_error_code)); if (REALM_UNLIKELY(!known_error_code)) { - logger.error("Unknown error code"); // Throws - return ClientError::bad_error_code; + return {ErrorCodes::SyncProtocolInvariantFailed, + util::format("Unknown error code %1 in ERROR message", info.raw_error_code)}; } ProtocolError error_code = ProtocolError(info.raw_error_code); if (REALM_UNLIKELY(!is_session_level_error(error_code))) { - logger.error("Not a session level error code"); // Throws - return ClientError::bad_error_code; + return {ErrorCodes::SyncProtocolInvariantFailed, + util::format("Received a non-session level error code %1", info.raw_error_code)}; } // Can't process debug hook actions once the Session is undergoing deactivation, since @@ -2516,24 +2473,24 @@ std::error_code Session::receive_error_message(const ProtocolErrorInfo& info) if (m_state == Active) { auto debug_action = call_debug_hook(SyncClientHookEvent::ErrorMessageReceived, info); if (debug_action == SyncClientHookAction::EarlyReturn) { - return {}; + return Status::OK(); } } - // For compensating write errors, we need to defer raising them to the SDK until after the server version - // containing the compensating write has appeared in a download message. + // For compensating write errors, we need to defer raising them to the SDK until after the server + // version containing the compensating write has appeared in a download message. if (error_code == ProtocolError::compensating_write) { // If the client is not active, the compensating writes will not be processed now, but will be // sent again the next time the client connects if (m_state == Active) { m_pending_compensating_write_errors.push_back(info); } - return {}; + return Status::OK(); } m_error_message_received = true; - suspend(SessionErrorInfo{info, make_error_code(error_code)}); - return {}; + suspend(SessionErrorInfo{info}); + return Status::OK(); } void Session::suspend(const SessionErrorInfo& info) @@ -2573,7 +2530,7 @@ void Session::suspend(const SessionErrorInfo& info) ensure_enlisted_to_send(); // Throws } -std::error_code Session::receive_test_command_response(request_ident_type ident, std::string_view body) +Status Session::receive_test_command_response(request_ident_type ident, std::string_view body) { logger.info("Received: TEST_COMMAND \"%1\" (session_ident=%2, request_ident=%3)", body, m_ident, ident); auto it = std::find_if(m_pending_test_commands.begin(), m_pending_test_commands.end(), @@ -2581,14 +2538,14 @@ std::error_code Session::receive_test_command_response(request_ident_type ident, return command.id == ident; }); if (it == m_pending_test_commands.end()) { - logger.error("No matching pending test command for id %1", ident); - return ClientError::bad_request_ident; + return {ErrorCodes::SyncProtocolInvariantFailed, + util::format("Received test command response for a non-existent ident %1", ident)}; } it->promise.emplace_value(std::string{body}); m_pending_test_commands.erase(it); - return {}; + return Status::OK(); } void Session::begin_resumption_delay(const ProtocolErrorInfo& error_info) @@ -2599,9 +2556,10 @@ void Session::begin_resumption_delay(const ProtocolErrorInfo& error_info) error_info.resumption_delay_interval); auto try_again_interval = m_try_again_delay_info.delay_interval(); if (ProtocolError(error_info.raw_error_code) == ProtocolError::session_closed) { - // FIXME With compensating writes the server sends this error after completing a bootstrap. Doing the normal - // backoff behavior would result in waiting up to 5 minutes in between each query change which is - // not acceptable latency. So for this error code alone, we hard-code a 1 second retry interval. + // FIXME With compensating writes the server sends this error after completing a bootstrap. Doing + // the normal backoff behavior would result in waiting up to 5 minutes in between each query + // change which is not acceptable latency. So for this error code alone, we hard-code a 1 second + // retry interval. try_again_interval = std::chrono::milliseconds{1000}; } logger.debug("Will attempt to resume session after %1 milliseconds", try_again_interval.count()); @@ -2624,53 +2582,58 @@ void Session::clear_resumption_delay_state() } } -bool ClientImpl::Session::check_received_sync_progress(const SyncProgress& progress, int& error_code) noexcept +Status ClientImpl::Session::check_received_sync_progress(const SyncProgress& progress) noexcept { const SyncProgress& a = m_progress; const SyncProgress& b = progress; // Latest server version must be weakly increasing throughout a session. if (b.latest_server_version.version < a.latest_server_version.version) { - error_code = 1; - return false; + return {ErrorCodes::SyncProtocolInvariantFailed, + "Latest server version in download must be weakly increasing throughout a session"}; } // Last integrated client version on server must be weakly increasing // throughout a session. if (b.upload.client_version < a.upload.client_version) { - error_code = 2; - return false; + return {ErrorCodes::SyncProtocolInvariantFailed, + "Last integrated client version on server must be weakly increasing throughout a session."}; } // Last integrated client version on server cannot be greater than the // latest client version in existence. if (b.upload.client_version > m_last_version_available) { - error_code = 3; - return false; + return {ErrorCodes::SyncProtocolInvariantFailed, "Last integrated client version on server cannot be greater " + "than the latest client version in existence."}; } // Download cursor must be weakly increasing throughout a session if (b.download.server_version < a.download.server_version) { - error_code = 4; - return false; + return {ErrorCodes::SyncProtocolInvariantFailed, + "Download cursor must be weakly increasing throughout a session"}; } // Download cursor cannot be greater than the latest server version in // existence. if (b.download.server_version > b.latest_server_version.version) { - error_code = 5; - return false; + return {ErrorCodes::SyncProtocolInvariantFailed, + "Download cursor cannot be greater than the latest server version in"}; } // The last integrated client version on the server at the position in the // server's history of the download cursor must be weakly increasing // throughout a session. if (b.download.last_integrated_client_version < a.download.last_integrated_client_version) { - error_code = 6; - return false; + return {ErrorCodes::SyncProtocolInvariantFailed, + "The last integrated client version on the server at the position in the server's " + "history of the " + "download cursor must be weakly increasing throughout a session."}; } // The last integrated client version on the server at the position in the // server's history of the download cursor cannot be greater than the latest // client version integrated on the server. if (b.download.last_integrated_client_version > b.upload.client_version) { - error_code = 7; - return false; + return {ErrorCodes::SyncProtocolInvariantFailed, + "The last integrated client version on the server at the position in the server's history of " + "the " + "download cursor cannot be greater than the latest client version integrated on the server."}; } - return true; + + return Status::OK(); } diff --git a/src/realm/sync/noinst/client_impl_base.hpp b/src/realm/sync/noinst/client_impl_base.hpp index 184f4c63286..fde629657bc 100644 --- a/src/realm/sync/noinst/client_impl_base.hpp +++ b/src/realm/sync/noinst/client_impl_base.hpp @@ -483,6 +483,7 @@ class ClientImpl::Connection { void websocket_connected_handler(const std::string& protocol); bool websocket_binary_message_received(util::Span data); void websocket_error_handler(); + bool websocket_closed_handler(bool, websocket::WebSocketError, std::string_view); bool websocket_closed_handler(bool, Status); connection_ident_type get_ident() const noexcept; @@ -564,10 +565,10 @@ class ClientImpl::Connection { void initiate_disconnect_wait(); void handle_disconnect_wait(Status status); void read_or_write_error(std::error_code ec, std::string_view msg); - void close_due_to_protocol_error(std::error_code, std::optional msg = std::nullopt); + void close_due_to_protocol_error(Status status); - void close_due_to_client_side_error(std::error_code, std::optional msg, IsFatal is_fatal, - ConnectionTerminationReason reason); + void close_due_to_network_error(Status, IsFatal, ConnectionTerminationReason, websocket::WebSocketError error); + void close_due_to_client_side_error(Status, IsFatal is_fatal, ConnectionTerminationReason reason); void close_due_to_server_side_error(ProtocolError, const ProtocolErrorInfo& info); void involuntary_disconnect(const SessionErrorInfo& info, ConnectionTerminationReason reason); void disconnect(const SessionErrorInfo& info); @@ -583,7 +584,6 @@ class ClientImpl::Connection { void receive_mark_message(session_ident_type, request_ident_type); void receive_unbound_message(session_ident_type); void receive_test_command_response(session_ident_type, request_ident_type, std::string_view body); - void handle_protocol_error(ClientProtocol::Error); // These are only called from Session class. void enlist_to_send(Session*); @@ -1210,21 +1210,20 @@ class ClientImpl::Session { void send_query_change_message(); void send_json_error_message(); void send_test_command_message(); - std::error_code receive_ident_message(SaltedFileIdent); + Status receive_ident_message(SaltedFileIdent); void receive_download_message(const SyncProgress&, std::uint_fast64_t downloadable_bytes, DownloadBatchState last_in_batch, int64_t query_version, const ReceivedChangesets&); - std::error_code receive_mark_message(request_ident_type); - std::error_code receive_unbound_message(); - std::error_code receive_error_message(const ProtocolErrorInfo& info); - std::error_code receive_query_error_message(int error_code, std::string_view message, int64_t query_version); - std::error_code receive_test_command_response(request_ident_type, std::string_view body); + Status receive_mark_message(request_ident_type); + Status receive_unbound_message(); + Status receive_error_message(const ProtocolErrorInfo& info); + Status receive_query_error_message(int error_code, std::string_view message, int64_t query_version); + Status receive_test_command_response(request_ident_type, std::string_view body); void initiate_rebind(); void reset_protocol_state() noexcept; void ensure_enlisted_to_send(); void enlist_to_send(); - bool check_received_sync_progress(const SyncProgress&) noexcept; - bool check_received_sync_progress(const SyncProgress&, int&) noexcept; + Status check_received_sync_progress(const SyncProgress&) noexcept; void check_for_upload_completion(); void check_for_download_completion(); @@ -1311,7 +1310,8 @@ inline void ClientImpl::Connection::voluntary_disconnect() { m_reconnect_info.update(ConnectionTerminationReason::closed_voluntarily, std::nullopt); constexpr bool try_again = true; - disconnect(SessionErrorInfo{ClientError::connection_closed, try_again}); // Throws + disconnect(SessionErrorInfo{Status{ErrorCodes::ConnectionClosed, "Connection closed voluntarily"}, try_again, + ProtocolError::connection_closed}); // Throws } inline void ClientImpl::Connection::involuntary_disconnect(const SessionErrorInfo& info, @@ -1631,12 +1631,6 @@ inline void ClientImpl::Session::enlist_to_send() m_conn.enlist_to_send(this); // Throws } -inline bool ClientImpl::Session::check_received_sync_progress(const SyncProgress& progress) noexcept -{ - int error_code = 0; // Dummy - return check_received_sync_progress(progress, error_code); -} - } // namespace sync } // namespace realm diff --git a/src/realm/sync/noinst/protocol_codec.hpp b/src/realm/sync/noinst/protocol_codec.hpp index 35d68d5c1b9..e200fda4cc8 100644 --- a/src/realm/sync/noinst/protocol_codec.hpp +++ b/src/realm/sync/noinst/protocol_codec.hpp @@ -223,9 +223,9 @@ class ClientProtocol { void parse_message_received(Connection& connection, std::string_view msg_data) { util::Logger& logger = connection.logger; - auto report_error = [&](Error err, const auto fmt, auto&&... args) { - logger.error(fmt, std::forward(args)...); - connection.handle_protocol_error(err); + auto report_error = [&](const auto fmt, auto&&... args) { + auto msg = util::format(fmt, std::forward(args)...); + connection.close_due_to_protocol_error({ErrorCodes::SyncProtocolInvariantFailed, msg}); }; HeaderLineParser msg(msg_data); @@ -234,7 +234,7 @@ class ClientProtocol { message_type = msg.read_next(); } catch (const ProtocolCodecException& e) { - return report_error(Error::bad_syntax, "Could not find message type in message: %1", e.what()); + return report_error("Could not find message type in message: %1", e.what()); } try { @@ -257,7 +257,7 @@ class ClientProtocol { bool unknown_error = !sync::get_protocol_error_message(error_code); if (unknown_error) { - return report_error(Error::bad_error_code, "Bad error code"); + return report_error("Bad error code"); } auto message = msg.read_sized_data(message_size); @@ -296,7 +296,6 @@ class ClientProtocol { if (query_string == json.end() || !query_string->is_string() || query_string->get().empty()) { return report_error( - Error::bad_syntax, "Missing/invalid partition query string in migrate to flexible sync error response"); } @@ -306,14 +305,12 @@ class ClientProtocol { if (auto rejected_updates = json.find("rejectedUpdates"); rejected_updates != json.end()) { if (!rejected_updates->is_array()) { return report_error( - Error::bad_syntax, "Compensating writes error list is not stored in an array as expected"); } for (const auto& rejected_update : *rejected_updates) { if (!rejected_update.is_object()) { return report_error( - Error::bad_syntax, "Compensating write error information is not stored in an object as expected"); } @@ -334,8 +331,8 @@ class ClientProtocol { catch (const nlohmann::json::exception& e) { // If any of the above json fields are not present, this is a fatal error // however, additional optional fields may be added in the future. - return report_error(Error::bad_syntax, "Failed to parse 'json_error' with error_code %1: '%2'", - info.raw_error_code, e.what()); + return report_error("Failed to parse 'json_error' with error_code %1: '%2'", info.raw_error_code, + e.what()); } connection.receive_error_message(info, session_ident); // Throws } @@ -372,14 +369,14 @@ class ClientProtocol { connection.receive_test_command_response(session_ident, request_ident, body); } else { - return report_error(Error::unknown_message, "Unknown input message type '%1'", msg_data); + return report_error("Unknown input message type '%1'", msg_data); } } catch (const ProtocolCodecException& e) { - return report_error(Error::bad_syntax, "Bad syntax in %1 message: %2", message_type, e.what()); + return report_error("Bad syntax in %1 message: %2", message_type, e.what()); } if (!msg.at_end()) { - return report_error(Error::bad_syntax, "wire protocol message had leftover data after being parsed"); + return report_error("wire protocol message had leftover data after being parsed"); } } @@ -388,9 +385,9 @@ class ClientProtocol { void parse_download_message(Connection& connection, HeaderLineParser& msg) { util::Logger& logger = connection.logger; - auto report_error = [&](Error err, const auto fmt, auto&&... args) { - logger.error(fmt, std::forward(args)...); - connection.handle_protocol_error(err); + auto report_error = [&](const auto fmt, auto&&... args) { + auto msg = util::format(fmt, std::forward(args)...); + connection.close_due_to_protocol_error({ErrorCodes::SyncProtocolInvariantFailed, std::move(msg)}); }; auto msg_with_header = msg.remaining(); @@ -413,7 +410,7 @@ class ClientProtocol { if (uncompressed_body_size > s_max_body_size) { auto header = msg_with_header.substr(0, msg_with_header.size() - msg.remaining().size()); - return report_error(Error::limits_exceeded, "Limits exceeded in input message '%1'", header); + return report_error("Limits exceeded in input message '%1'", header); } std::unique_ptr uncompressed_body_buffer; @@ -425,7 +422,7 @@ class ClientProtocol { {uncompressed_body_buffer.get(), uncompressed_body_size}); if (ec) { - return report_error(Error::bad_decompression, "compression::inflate: %1", ec.message()); + return report_error("compression::inflate: %1", ec.message()); } msg = HeaderLineParser(std::string_view(uncompressed_body_buffer.get(), uncompressed_body_size)); @@ -448,12 +445,10 @@ class ClientProtocol { auto changeset_size = msg.read_next(); if (changeset_size > msg.bytes_remaining()) { - return report_error(Error::bad_changeset_size, "Bad changeset size %1 > %2", changeset_size, - msg.bytes_remaining()); + return report_error("Bad changeset size %1 > %2", changeset_size, msg.bytes_remaining()); } if (cur_changeset.remote_version == 0) { - return report_error(Error::bad_server_version, - "Server version in downloaded changeset cannot be zero"); + return report_error("Server version in downloaded changeset cannot be zero"); } auto changeset_data = msg.read_sized_data(changeset_size); logger.debug("Received: DOWNLOAD CHANGESET(session_ident=%1, server_version=%2, " @@ -601,8 +596,8 @@ class ServerProtocol { connection.receive_ping(timestamp, rtt); } catch (const ProtocolCodecException& e) { - connection.logger.error("Bad syntax in ping message: %1", e.what()); - connection.handle_protocol_error(Error::bad_syntax); + connection.close_due_to_protocol_error( + {ErrorCodes::SyncProtocolInvariantFailed, util::format("Bad syntax in ping message: %1", e.what())}); } } @@ -623,9 +618,9 @@ class ServerProtocol { { auto& logger = connection.logger; - auto report_error = [&](Error err, const auto fmt, auto&&... args) { - logger.error(fmt, std::forward(args)...); - connection.handle_protocol_error(err); + auto report_error = [&](const auto fmt, auto&&... args) { + auto msg = util::format(fmt, std::forward(args)...); + connection.close_due_to_protocol_error({ErrorCodes::SyncProtocolInvariantFailed, std::move(msg)}); }; HeaderLineParser msg(msg_data); @@ -634,7 +629,7 @@ class ServerProtocol { message_type = msg.read_next(); } catch (const ProtocolCodecException& e) { - return report_error(Error::bad_syntax, "Could not find message type in message: %1", e.what()); + return report_error("Could not find message type in message: %1", e.what()); } try { @@ -651,8 +646,7 @@ class ServerProtocol { std::size_t body_size = (is_body_compressed ? compressed_body_size : uncompressed_body_size); if (body_size > s_max_body_size) { auto header = msg_with_header.substr(0, msg_with_header.size() - msg.bytes_remaining()); - return report_error(Error::limits_exceeded, - "Body size of upload message is too large. Raw header: %1", header); + return report_error("Body size of upload message is too large. Raw header: %1", header); } @@ -666,7 +660,7 @@ class ServerProtocol { compressed_body, {uncompressed_body_buffer.get(), uncompressed_body_size}); if (ec) { - return report_error(Error::bad_decompression, "compression::inflate: %1", ec.message()); + return report_error("compression::inflate: %1", ec.message()); } msg = HeaderLineParser(std::string_view(uncompressed_body_buffer.get(), uncompressed_body_size)); @@ -694,12 +688,11 @@ class ServerProtocol { changeset_size = msg.read_next(); } catch (const ProtocolCodecException& e) { - return report_error(Error::bad_changeset_header_syntax, "Bad changeset header syntax: %1", - e.what()); + return report_error("Bad changeset header syntax: %1", e.what()); } if (changeset_size > msg.bytes_remaining()) { - return report_error(Error::bad_changeset_size, "Bad changeset size"); + return report_error("Bad changeset size"); } upload_changeset.changeset = msg.read_sized_data(changeset_size); @@ -741,14 +734,13 @@ class ServerProtocol { auto is_subserver = msg.read_next('\n'); if (path_size == 0) { - return report_error(Error::bad_syntax, "Path size in BIND message is zero"); + return report_error("Path size in BIND message is zero"); } if (path_size > s_max_path_size) { - return report_error(Error::limits_exceeded, "Path size in BIND message is too large"); + return report_error("Path size in BIND message is too large"); } if (signed_user_token_size > s_max_signed_user_token_size) { - return report_error(Error::limits_exceeded, - "Signed user token size in BIND message is too large"); + return report_error("Signed user token size in BIND message is too large"); } auto path = msg.read_sized_data(path_size); @@ -784,11 +776,11 @@ class ServerProtocol { connection.receive_error_message(session_ident, error_code, json_raw); } else { - return report_error(Error::unknown_message, "unknown message type %1", message_type); + return report_error("unknown message type %1", message_type); } } catch (const ProtocolCodecException& e) { - return report_error(Error::bad_syntax, "bad syntax in %1 message: %2", message_type, e.what()); + return report_error("bad syntax in %1 message: %2", message_type, e.what()); } } diff --git a/src/realm/sync/noinst/server/server.cpp b/src/realm/sync/noinst/server/server.cpp index 78736073cb9..a8e338cb10f 100644 --- a/src/realm/sync/noinst/server/server.cpp +++ b/src/realm/sync/noinst/server/server.cpp @@ -1148,11 +1148,10 @@ class SyncConnection : public websocket::Config { bool websocket_binary_message_received(const char* data, size_t size) final override { - std::error_code ec; using sf = _impl::SimulatedFailure; - if (sf::trigger(sf::sync_server__read_head, ec)) { + if (sf::check_trigger(sf::sync_server__read_head)) { // Suicide - read_error(ec); + read_error(sf::sync_server__read_head); return false; } // After a connection level error has occurred, all incoming messages @@ -1275,7 +1274,7 @@ class SyncConnection : public websocket::Config { void initiate_pong_output_buffer(); - void handle_protocol_error(ServerProtocol::Error error); + void close_due_to_protocol_error(Status status); void receive_bind_message(session_ident_type, std::string path, std::string signed_user_token, bool need_client_file_ident, bool is_subserver); @@ -4246,32 +4245,11 @@ void SyncConnection::enlist_to_send(Session* sess) noexcept m_send_trigger->trigger(); } - -void SyncConnection::handle_protocol_error(ServerProtocol::Error error) +void SyncConnection::close_due_to_protocol_error(Status) { - switch (error) { - case ServerProtocol::Error::unknown_message: - protocol_error(ProtocolError::unknown_message); // Throws - break; - case ServerProtocol::Error::bad_syntax: - protocol_error(ProtocolError::bad_syntax); // Throws - break; - case ServerProtocol::Error::limits_exceeded: - protocol_error(ProtocolError::limits_exceeded); // Throws - break; - case ServerProtocol::Error::bad_decompression: - protocol_error(ProtocolError::bad_decompression); // Throws - break; - case ServerProtocol::Error::bad_changeset_header_syntax: - protocol_error(ProtocolError::bad_changeset_header_syntax); // Throws - break; - case ServerProtocol::Error::bad_changeset_size: - protocol_error(ProtocolError::bad_changeset_size); // Throws - break; - } + protocol_error(ProtocolError::bad_syntax); } - void SyncConnection::receive_bind_message(session_ident_type session_ident, std::string path, std::string signed_user_token, bool need_client_file_ident, bool is_subserver) diff --git a/src/realm/sync/protocol.cpp b/src/realm/sync/protocol.cpp index 9207c335eaa..24e1e5c61ea 100644 --- a/src/realm/sync/protocol.cpp +++ b/src/realm/sync/protocol.cpp @@ -151,6 +151,117 @@ const char* get_protocol_error_message(int error_code) noexcept return nullptr; } + +Status protocol_error_to_status(int raw_error_code, std::string_view msg) +{ + auto err_code = [&] { + switch (ProtocolError(raw_error_code)) { + case ProtocolError::connection_closed: + return ErrorCodes::ConnectionClosed; + case ProtocolError::other_error: + return ErrorCodes::RuntimeError; + case ProtocolError::unknown_message: + return ErrorCodes::SyncProtocolInvariantFailed; + case ProtocolError::bad_syntax: + return ErrorCodes::SyncProtocolInvariantFailed; + case ProtocolError::limits_exceeded: + REALM_UNREACHABLE(); + case ProtocolError::wrong_protocol_version: + return ErrorCodes::SyncProtocolNegotiationFailed; + case ProtocolError::bad_session_ident: + return ErrorCodes::SyncProtocolInvariantFailed; + case ProtocolError::reuse_of_session_ident: + return ErrorCodes::SyncProtocolInvariantFailed; + case ProtocolError::bound_in_other_session: + return ErrorCodes::SyncProtocolInvariantFailed; + case ProtocolError::bad_message_order: + return ErrorCodes::SyncProtocolInvariantFailed; + case ProtocolError::bad_decompression: + return ErrorCodes::RuntimeError; + case ProtocolError::bad_changeset_header_syntax: + return ErrorCodes::SyncProtocolInvariantFailed; + case ProtocolError::bad_changeset_size: + return ErrorCodes::SyncProtocolInvariantFailed; + case ProtocolError::switch_to_flx_sync: + return ErrorCodes::WrongSyncType; + case ProtocolError::switch_to_pbs: + return ErrorCodes::WrongSyncType; + + case ProtocolError::session_closed: + REALM_UNREACHABLE(); + case ProtocolError::other_session_error: + return ErrorCodes::RuntimeError; + case ProtocolError::token_expired: + REALM_UNREACHABLE(); + case ProtocolError::bad_authentication: + REALM_UNREACHABLE(); + case ProtocolError::illegal_realm_path: + return ErrorCodes::BadSyncPartitionValue; + case ProtocolError::no_such_realm: + REALM_UNREACHABLE(); + case ProtocolError::permission_denied: + return ErrorCodes::SyncPermissionDenied; + case ProtocolError::bad_server_file_ident: + REALM_UNREACHABLE(); + case ProtocolError::bad_client_file_ident: + return ErrorCodes::SyncClientResetRequired; + case ProtocolError::bad_server_version: + return ErrorCodes::SyncClientResetRequired; + case ProtocolError::bad_client_version: + return ErrorCodes::SyncClientResetRequired; + case ProtocolError::diverging_histories: + return ErrorCodes::SyncClientResetRequired; + case ProtocolError::bad_changeset: + return ErrorCodes::BadChangeset; + case ProtocolError::partial_sync_disabled: + REALM_UNREACHABLE(); + case ProtocolError::unsupported_session_feature: + REALM_UNREACHABLE(); + case ProtocolError::bad_origin_file_ident: + return ErrorCodes::SyncProtocolInvariantFailed; + case ProtocolError::bad_client_file: + return ErrorCodes::SyncClientResetRequired; + case ProtocolError::server_file_deleted: + REALM_UNREACHABLE(); + case ProtocolError::client_file_blacklisted: + REALM_UNREACHABLE(); + case ProtocolError::user_blacklisted: + REALM_UNREACHABLE(); + case ProtocolError::transact_before_upload: + REALM_UNREACHABLE(); + case ProtocolError::client_file_expired: + return ErrorCodes::SyncClientResetRequired; + case ProtocolError::user_mismatch: + return ErrorCodes::SyncUserMismatch; + case ProtocolError::too_many_sessions: + REALM_UNREACHABLE(); + case ProtocolError::invalid_schema_change: + return ErrorCodes::InvalidSchemaChange; + case ProtocolError::bad_query: + return ErrorCodes::InvalidSubscriptionQuery; + case ProtocolError::object_already_exists: + return ErrorCodes::ObjectAlreadyExists; + case ProtocolError::server_permissions_changed: + return ErrorCodes::SyncServerPermissionsChanged; + case ProtocolError::initial_sync_not_completed: + return ErrorCodes::ConnectionClosed; + case ProtocolError::write_not_allowed: + return ErrorCodes::SyncWriteNotAllowed; + case ProtocolError::compensating_write: + return ErrorCodes::SyncCompensatingWrite; + case ProtocolError::migrate_to_flx: + return ErrorCodes::WrongSyncType; + case ProtocolError::bad_progress: + return ErrorCodes::SyncProtocolInvariantFailed; + case ProtocolError::revert_to_pbs: + return ErrorCodes::WrongSyncType; + } + return ErrorCodes::UnknownError; + }(); + + return {err_code, msg}; +} + const std::error_category& protocol_error_category() noexcept { return g_error_category; diff --git a/src/realm/sync/protocol.hpp b/src/realm/sync/protocol.hpp index 583d2074a1f..c589bbd40d4 100644 --- a/src/realm/sync/protocol.hpp +++ b/src/realm/sync/protocol.hpp @@ -247,49 +247,6 @@ struct ResumptionDelayInfo { int delay_jitter_divisor = 4; }; -struct ProtocolErrorInfo { - enum class Action { - NoAction, - ProtocolViolation, - ApplicationBug, - Warning, - Transient, - DeleteRealm, - ClientReset, - ClientResetNoRecovery, - MigrateToFLX, - RevertToPBS - }; - - ProtocolErrorInfo() = default; - ProtocolErrorInfo(int error_code, const std::string& msg, bool do_try_again) - : raw_error_code(error_code) - , message(msg) - , try_again(do_try_again) - , client_reset_recovery_is_disabled(false) - , should_client_reset(util::none) - , server_requests_action(Action::NoAction) - { - } - int raw_error_code = 0; - std::string message; - bool try_again = false; - bool client_reset_recovery_is_disabled = false; - std::optional should_client_reset; - std::optional log_url; - version_type compensating_write_server_version = 0; - version_type compensating_write_rejected_client_version = 0; - std::vector compensating_writes; - std::optional resumption_delay_interval; - Action server_requests_action; - std::optional migration_query_string; - - bool is_fatal() const - { - return !try_again; - } -}; - /// \brief Protocol errors discovered by the server, and reported to the client /// by way of ERROR messages. @@ -361,6 +318,8 @@ enum class ProtocolError { // clang-format on }; +Status protocol_error_to_status(int raw_error_code, std::string_view msg); + constexpr bool is_session_level_error(ProtocolError); /// Returns null if the specified protocol error code is not defined by @@ -371,6 +330,58 @@ const std::error_category& protocol_error_category() noexcept; std::error_code make_error_code(ProtocolError) noexcept; +struct ProtocolErrorInfo { + enum class Action { + NoAction, + ProtocolViolation, + ApplicationBug, + Warning, + Transient, + DeleteRealm, + ClientReset, + ClientResetNoRecovery, + MigrateToFLX, + RevertToPBS + }; + + ProtocolErrorInfo() = default; + ProtocolErrorInfo(int error_code, const std::string& msg, bool do_try_again) + : raw_error_code(error_code) + , message(msg) + , try_again(do_try_again) + , client_reset_recovery_is_disabled(false) + , should_client_reset(util::none) + , server_requests_action(Action::NoAction) + { + } + int raw_error_code = 0; + std::string message; + bool try_again = false; + bool client_reset_recovery_is_disabled = false; + std::optional should_client_reset; + std::optional log_url; + version_type compensating_write_server_version = 0; + version_type compensating_write_rejected_client_version = 0; + std::vector compensating_writes; + std::optional resumption_delay_interval; + Action server_requests_action; + std::optional migration_query_string; + + bool is_fatal() const + { + return !try_again; + } + + std::optional to_protocol_error() const + { + if (get_protocol_error_message(raw_error_code)) { + return static_cast(raw_error_code); + } + return std::nullopt; + } +}; + + } // namespace sync } // namespace realm diff --git a/src/realm/sync/socket_provider.hpp b/src/realm/sync/socket_provider.hpp index 265143c0331..62d67beeaa2 100644 --- a/src/realm/sync/socket_provider.hpp +++ b/src/realm/sync/socket_provider.hpp @@ -31,6 +31,9 @@ #include namespace realm::sync { +namespace websocket { +enum class WebSocketError; +} struct WebSocketEndpoint; struct WebSocketInterface; @@ -238,15 +241,16 @@ struct WebSocketObserver { /// /// @param was_clean Was the TCP connection closed after the WebSocket closing /// handshake was completed. - /// @param status A Status object containing the WebSocket status code and the - /// reason string why the connection was closed. + /// @param code Either a WebSocketError or the numeric code sent in the close frame. + /// @param message Reason string why the connection was closed. /// /// @return bool designates whether the WebSocket object has been destroyed /// during the execution of this function. The normal return value is /// True to indicate the WebSocket object is no longer valid. If False /// is returned, the WebSocket object will be destroyed at some point /// in the future. - virtual bool websocket_closed_handler(bool was_clean, Status status) = 0; + virtual bool websocket_closed_handler(bool was_clean, websocket::WebSocketError code, + std::string_view message) = 0; }; } // namespace realm::sync diff --git a/test/object-store/c_api/c_api.cpp b/test/object-store/c_api/c_api.cpp index e5947c31a44..8f03f2a655d 100644 --- a/test/object-store/c_api/c_api.cpp +++ b/test/object-store/c_api/c_api.cpp @@ -6183,9 +6183,10 @@ TEST_CASE("C API app: websocket provider", "[c_api][sync][app]") { return m_observer->websocket_binary_message_received(data); } - bool websocket_closed_handler(bool was_clean, Status status) override + bool websocket_closed_handler(bool was_clean, websocket::WebSocketError code, + std::string_view message) override { - return m_observer->websocket_closed_handler(was_clean, std::move(status)); + return m_observer->websocket_closed_handler(was_clean, code, message); } private: diff --git a/test/object-store/sync/app.cpp b/test/object-store/sync/app.cpp index 21dc8dc07fe..801dcf7f235 100644 --- a/test/object-store/sync/app.cpp +++ b/test/object-store/sync/app.cpp @@ -2472,7 +2472,7 @@ TEST_CASE("app: sync integration", "[sync][app][baas]") { SyncTestFile r_config(user1, partition, schema); // Override the default r_config.sync_config->error_handler = [&](std::shared_ptr, SyncError error) { - if (error.get_system_error() == sync::make_error_code(realm::sync::ProtocolError::bad_authentication)) { + if (error.code() == ErrorCodes::AuthError) { util::format(std::cerr, "Websocket redirect test: User logged out\n"); std::unique_lock lk(logout_mutex); logged_out = true; @@ -2769,8 +2769,7 @@ TEST_CASE("app: sync integration", "[sync][app][baas]") { std::atomic sync_error_handler_called{false}; config.sync_config->error_handler = [&](std::shared_ptr, SyncError error) { sync_error_handler_called.store(true); - REQUIRE(error.get_system_error() == - sync::make_error_code(realm::sync::ProtocolError::bad_authentication)); + REQUIRE(error.code() == ErrorCodes::AuthError); REQUIRE(error.reason() == "Unable to refresh the user access token."); }; auto r = Realm::get_shared_realm(config); @@ -2869,8 +2868,7 @@ TEST_CASE("app: sync integration", "[sync][app][baas]") { config.sync_config->error_handler = [&](std::shared_ptr, SyncError error) { std::lock_guard lock(mtx); sync_error_handler_called.store(true); - REQUIRE(error.get_system_error() == - sync::make_error_code(realm::sync::ProtocolError::bad_authentication)); + REQUIRE(error.code() == ErrorCodes::AuthError); REQUIRE(error.reason() == "Unable to refresh the user access token."); }; @@ -3100,7 +3098,7 @@ TEST_CASE("app: sync integration", "[sync][app][baas]") { r->commit_transaction(); auto error = wait_for_future(std::move(pf.future), std::chrono::minutes(5)).get(); - REQUIRE(error.get_system_error() == make_error_code(sync::ProtocolError::limits_exceeded)); + REQUIRE(error.code() == ErrorCodes::LimitExceeded); REQUIRE(error.reason() == "Sync websocket closed because the server received a message that was too large: " "read limited at 16777217 bytes"); REQUIRE(error.is_client_reset_requested()); diff --git a/test/object-store/sync/client_reset.cpp b/test/object-store/sync/client_reset.cpp index 1ef07b67ba2..a0fab0716d7 100644 --- a/test/object-store/sync/client_reset.cpp +++ b/test/object-store/sync/client_reset.cpp @@ -79,7 +79,7 @@ struct StringMaker { if (!value) { return "No SyncError"; } - return realm::util::format("SyncError(%1), is_fatal: %2, with message: '%3'", value->get_system_error(), + return realm::util::format("SyncError(%1), is_fatal: %2, with message: '%3'", value->code_string(), value->is_fatal, value->reason()); } }; @@ -132,7 +132,7 @@ TEST_CASE("sync: large reset with recovery is restartable", "[client reset][baas 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, SyncError err) { - if (err.get_system_error() == util::make_error_code(util::MiscExtErrors::end_of_input)) { + if (err.code() == ErrorCodes::ConnectionClosed) { return; } @@ -141,7 +141,7 @@ TEST_CASE("sync: large reset with recovery is restartable", "[client reset][baas return; } - FAIL(util::format("got error from server: %1: %2", err.get_system_error().value(), err.what())); + FAIL(util::format("got error from server: %1", err.to_status())); }; auto realm = Realm::get_shared_realm(realm_config); @@ -227,7 +227,7 @@ TEST_CASE("sync: pending client resets are cleared when downloads are complete", 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, SyncError err) { - if (err.get_system_error() == sync::websocket::WebSocketError::websocket_read_error) { + if (err.code() == ErrorCodes::ConnectionClosed) { return; } @@ -236,7 +236,7 @@ TEST_CASE("sync: pending client resets are cleared when downloads are complete", return; } - FAIL(util::format("got error from server: %1: %2", err.get_system_error().value(), err.what())); + FAIL(util::format("got error from server: %1", err.to_status())); }; auto realm = Realm::get_shared_realm(realm_config); @@ -896,8 +896,10 @@ TEST_CASE("sync: client reset", "[client reset][baas]") { session = test_app_session.app()->sync_manager()->get_existing_session(temp_config.path); REQUIRE(session); } - sync::SessionErrorInfo synthetic(sync::make_error_code(sync::ProtocolError::bad_client_file), - "A fake client reset error", false); + sync::SessionErrorInfo synthetic( + sync::protocol_error_to_status(static_cast(sync::ProtocolError::bad_client_file), + "A fake client reset error"), + false); synthetic.server_requests_action = sync::ProtocolErrorInfo::Action::ClientReset; SyncSession::OnlyForTesting::handle_error(*session, std::move(synthetic)); @@ -2408,8 +2410,7 @@ struct Remove { util::Optional pk; }; -struct Clear { -}; +struct Clear {}; struct RemoveObject { RemoveObject(std::string_view name, util::Optional key) diff --git a/test/object-store/sync/flx_migration.cpp b/test/object-store/sync/flx_migration.cpp index cfa152d6905..f8ca2ee764e 100644 --- a/test/object-store/sync/flx_migration.cpp +++ b/test/object-store/sync/flx_migration.cpp @@ -236,7 +236,7 @@ TEST_CASE("Test server migration and rollback", "[flx][migration][baas]") { }; auto flx_realm = Realm::get_shared_realm(flx_config); auto err = wait_for_future(std::move(err_future), std::chrono::seconds(30)).get(); - REQUIRE(err.get_system_error() == make_error_code(sync::ProtocolError::switch_to_pbs)); + REQUIRE(err.code() == ErrorCodes::WrongSyncType); REQUIRE(err.server_requests_action == sync::ProtocolErrorInfo::Action::ApplicationBug); } @@ -695,7 +695,7 @@ TEST_CASE("Update to native FLX after migration", "[flx][migration][baas]") { }; auto flx_realm = Realm::get_shared_realm(flx_config); auto err = wait_for_future(std::move(err_future), std::chrono::seconds(30)).get(); - REQUIRE(err.get_system_error() == make_error_code(sync::ProtocolError::switch_to_pbs)); + REQUIRE(err.code() == ErrorCodes::WrongSyncType); REQUIRE(err.server_requests_action == sync::ProtocolErrorInfo::Action::ApplicationBug); } } diff --git a/test/object-store/sync/flx_sync.cpp b/test/object-store/sync/flx_sync.cpp index a8f37c6c9e6..5dbd9c020e2 100644 --- a/test/object-store/sync/flx_sync.cpp +++ b/test/object-store/sync/flx_sync.cpp @@ -634,8 +634,7 @@ TEST_CASE("flx: client reset", "[sync][flx][app][baas][client reset]") { auto sync_error = wait_for_future(std::move(err_future)).get(); REQUIRE(before_reset_count == 1); REQUIRE(after_reset_count == 0); - REQUIRE(sync_error.get_system_error() == - sync::make_error_code(sync::ClientError::auto_client_reset_failure)); + REQUIRE(sync_error.code() == ErrorCodes::AutoClientResetFailed); REQUIRE(sync_error.is_client_reset_requested()); local_realm->refresh(); auto table = local_realm->read_group().get_table("class_TopLevel"); @@ -681,8 +680,7 @@ TEST_CASE("flx: client reset", "[sync][flx][app][baas][client reset]") { auto sync_error = wait_for_future(std::move(err_future)).get(); REQUIRE(before_reset_count == 1); REQUIRE(after_reset_count == 0); - REQUIRE(sync_error.get_system_error() == - sync::make_error_code(sync::ClientError::auto_client_reset_failure)); + REQUIRE(sync_error.code() == ErrorCodes::AutoClientResetFailed); REQUIRE(sync_error.is_client_reset_requested()); local_realm->refresh(); auto table = local_realm->read_group().get_table("class_TopLevel"); @@ -709,8 +707,7 @@ TEST_CASE("flx: client reset", "[sync][flx][app][baas][client reset]") { auto sync_error = wait_for_future(std::move(error_future2)).get(); REQUIRE(before_reset_count == 2); REQUIRE(after_reset_count == 0); - REQUIRE(sync_error.get_system_error() == - sync::make_error_code(sync::ClientError::auto_client_reset_failure)); + REQUIRE(sync_error.code() == ErrorCodes::AutoClientResetFailed); REQUIRE(sync_error.is_client_reset_requested()); } @@ -850,8 +847,7 @@ TEST_CASE("flx: client reset", "[sync][flx][app][baas][client reset]") { ->on_post_reset([&, err_future = std::move(error_future)](SharedRealm) mutable { auto sync_error = wait_for_future(std::move(err_future)).get(); INFO(sync_error.reason()); - CHECK(sync_error.get_system_error() == - sync::make_error_code(sync::ClientError::auto_client_reset_failure)); + REQUIRE(sync_error.code() == ErrorCodes::AutoClientResetFailed); }) ->run(); } @@ -884,7 +880,7 @@ TEST_CASE("flx: client reset", "[sync][flx][app][baas][client reset]") { // Client reset fails due to sync client not being able to create the fresh realm. auto sync_error = wait_for_future(std::move(error_future)).get(); - CHECK(sync_error.get_system_error() == sync::make_error_code(sync::ClientError::auto_client_reset_failure)); + REQUIRE(sync_error.code() == ErrorCodes::AutoClientResetFailed); // Open the realm again. This should not crash. { @@ -894,8 +890,7 @@ TEST_CASE("flx: client reset", "[sync][flx][app][baas][client reset]") { auto realm_post_reset = Realm::get_shared_realm(config_copy); auto sync_error = wait_for_future(std::move(err_future)).get(); - CHECK(sync_error.get_system_error() == - sync::make_error_code(sync::ClientError::auto_client_reset_failure)); + REQUIRE(sync_error.code() == ErrorCodes::AutoClientResetFailed); } } @@ -1264,8 +1259,7 @@ TEST_CASE("flx: uploading an object that is out-of-view results in compensating auto validate_sync_error = [&](const SyncError& sync_error, Mixed expected_pk, const char* expected_object_name, const std::string& error_msg_fragment) { - CHECK(sync_error.get_system_error() == sync::make_error_code(sync::ProtocolError::compensating_write)); - CHECK(sync_error.is_session_level_protocol_error()); + CHECK(sync_error.code() == ErrorCodes::SyncCompensatingWrite); CHECK(!sync_error.is_client_reset_requested()); CHECK(sync_error.compensating_writes_info.size() == 1); CHECK(sync_error.server_requests_action == sync::ProtocolErrorInfo::Action::Warning); @@ -2255,7 +2249,7 @@ TEST_CASE("flx: connect to FLX as PBS returns an error", "[sync][flx][app][baas] return static_cast(sync_error); }); - CHECK(sync_error->get_system_error() == make_error_code(sync::ProtocolError::switch_to_flx_sync)); + CHECK(sync_error->code() == ErrorCodes::WrongSyncType); CHECK(sync_error->server_requests_action == sync::ProtocolErrorInfo::Action::ApplicationBug); } @@ -2297,7 +2291,7 @@ TEST_CASE("flx: connect to PBS as FLX returns an error", "[sync][flx][app][baas] return static_cast(sync_error); }); - CHECK(sync_error->get_system_error() == make_error_code(sync::ProtocolError::switch_to_pbs)); + CHECK(sync_error->code() == ErrorCodes::WrongSyncType); CHECK(sync_error->server_requests_action == sync::ProtocolErrorInfo::Action::ApplicationBug); } @@ -2451,7 +2445,7 @@ TEST_CASE("flx: bootstrap batching prevents orphan documents", "[sync][flx][app] auto realm = Realm::get_shared_realm(interrupted_realm_config); const auto& error = error_pf.future.get(); REQUIRE(error.is_fatal); - REQUIRE(error.get_system_error() == make_error_code(sync::ClientError::bad_changeset)); + REQUIRE(error.code() == ErrorCodes::BadChangeset); } SECTION("interrupted before final bootstrap message") { @@ -2776,11 +2770,11 @@ TEST_CASE("flx: asymmetric sync", "[sync][flx][app][baas]") { ++error_count; if (error_count == 1) { // Bad changeset detected by the client. - CHECK(err.get_system_error() == sync::make_error_code(sync::ClientError::bad_changeset)); + CHECK(err.code() == ErrorCodes::BadChangeset); } else if (error_count == 2) { // Server asking for a client reset. - CHECK(err.get_system_error() == sync::make_error_code(sync::ProtocolError::bad_client_file)); + CHECK(err.code() == ErrorCodes::SyncClientResetRequired); CHECK(err.is_client_reset_requested()); promise.get_promise().emplace_value(std::move(err)); } @@ -2895,10 +2889,8 @@ TEST_CASE("flx: asymmetric sync - dev mode", "[sync][flx][app][baas]") { CppContext c(realm); realm->begin_transaction(); - Object::create(c, realm, "Asymmetric", - std::any(AnyDict{{"_id", foo_obj_id}, {"location", "foo"s}})); - Object::create(c, realm, "Asymmetric", - std::any(AnyDict{{"_id", bar_obj_id}, {"location", "bar"s}})); + Object::create(c, realm, "Asymmetric", std::any(AnyDict{{"_id", foo_obj_id}, {"location", "foo"s}})); + Object::create(c, realm, "Asymmetric", std::any(AnyDict{{"_id", bar_obj_id}, {"location", "bar"s}})); realm->commit_transaction(); wait_for_upload(*realm); @@ -2921,11 +2913,11 @@ TEST_CASE("flx: send client error", "[sync][flx][app][baas]") { ++error_count; if (error_count == 1) { // Bad changeset detected by the client. - CHECK(err.get_system_error() == sync::make_error_code(sync::ClientError::bad_changeset)); + CHECK(err.code() == ErrorCodes::BadChangeset); } else if (error_count == 2) { // Server asking for a client reset. - CHECK(err.get_system_error() == sync::make_error_code(sync::ProtocolError::bad_client_file)); + CHECK(err.code() == ErrorCodes::SyncClientResetRequired); CHECK(err.is_client_reset_requested()); promise.get_promise().emplace_value(std::move(err)); } @@ -3440,7 +3432,7 @@ TEST_CASE("flx: compensating write errors get re-sent across sessions", "[sync][ config.sync_config->error_handler = [&](std::shared_ptr, SyncError error) { std::unique_lock lk(errors_mutex); - REQUIRE(error.get_system_error() == make_error_code(sync::ProtocolError::compensating_write)); + REQUIRE(error.code() == ErrorCodes::SyncCompensatingWrite); for (const auto& compensating_write : error.compensating_writes_info) { auto tracked_error = std::find_if(error_to_download_version.begin(), error_to_download_version.end(), [&](const auto& pair) { diff --git a/test/object-store/sync/session/session.cpp b/test/object-store/sync/session/session.cpp index 21d2584ad3c..92808ed1404 100644 --- a/test/object-store/sync/session/session.cpp +++ b/test/object-store/sync/session/session.cpp @@ -401,38 +401,23 @@ TEST_CASE("sync: error handling", "[sync]") { }); SECTION("Doesn't treat unknown system errors as being fatal") { - std::error_code code = std::error_code{EBADF, std::generic_category()}; - sync::SessionErrorInfo err{code, "Not a real error message", true}; + sync::SessionErrorInfo err{Status{ErrorCodes::UnknownError, "unknown error"}, true}; err.server_requests_action = ProtocolErrorInfo::Action::Transient; SyncSession::OnlyForTesting::handle_error(*session, std::move(err)); CHECK(!sessions_are_inactive(*session)); } SECTION("Properly handles a client reset error") { - int code = 0; util::Optional final_error; error_handler = [&](auto, SyncError error) { final_error = std::move(error); }; - SECTION("for bad_server_file_ident") { - code = static_cast(ProtocolError::bad_server_file_ident); - } - - SECTION("for bad_client_file_ident") { - code = static_cast(ProtocolError::bad_client_file_ident); - } - - SECTION("for bad_server_version") { - code = static_cast(ProtocolError::bad_server_version); - } - - SECTION("for diverging_histories") { - code = static_cast(ProtocolError::diverging_histories); - } + auto code = + static_cast(GENERATE(ProtocolError::bad_server_file_ident, ProtocolError::bad_client_file_ident, + ProtocolError::bad_server_version, ProtocolError::diverging_histories)); - sync::SessionErrorInfo initial_error{std::error_code{code, realm::sync::protocol_error_category()}, - "Something bad happened", true}; + sync::SessionErrorInfo initial_error{sync::protocol_error_to_status(code, "Something bad happened"), true}; initial_error.server_requests_action = ProtocolErrorInfo::Action::ClientReset; std::time_t just_before_raw = std::time(nullptr); SyncSession::OnlyForTesting::handle_error(*session, std::move(initial_error)); @@ -477,7 +462,6 @@ struct RegularUser { TEMPLATE_TEST_CASE("sync: stop policy behavior", "[sync]", RegularUser) { - using ProtocolError = realm::sync::ProtocolError; const std::string dummy_auth_url = "https://realm.example.org"; if (!EventLoop::has_implementation()) return; @@ -552,9 +536,8 @@ TEMPLATE_TEST_CASE("sync: stop policy behavior", "[sync]", RegularUser) } SECTION("transitions to Inactive if a fatal error occurs") { - std::error_code code = - std::error_code{static_cast(ProtocolError::bad_syntax), realm::sync::protocol_error_category()}; - sync::SessionErrorInfo err{code, "Not a real error message", false}; + Status err_status(ErrorCodes::SyncProtocolInvariantFailed, "Not a real error message"); + sync::SessionErrorInfo err{err_status, false}; err.server_requests_action = realm::sync::ProtocolErrorInfo::Action::ProtocolViolation; SyncSession::OnlyForTesting::handle_error(*session, std::move(err)); CHECK(sessions_are_inactive(*session)); @@ -564,9 +547,8 @@ TEMPLATE_TEST_CASE("sync: stop policy behavior", "[sync]", RegularUser) SECTION("ignores non-fatal errors and does not transition to Inactive") { // Fire a simulated *non-fatal* error. - std::error_code code = - std::error_code{static_cast(ProtocolError::other_error), realm::sync::protocol_error_category()}; - sync::SessionErrorInfo err{code, "Not a real error message", true}; + Status err_status(ErrorCodes::RuntimeError, "Not a real error message"); + sync::SessionErrorInfo err{err_status, true}; err.server_requests_action = realm::sync::ProtocolErrorInfo::Action::Transient; SyncSession::OnlyForTesting::handle_error(*session, std::move(err)); REQUIRE(session->state() == SyncSession::State::Dying); diff --git a/test/object-store/sync/session/wait_for_completion.cpp b/test/object-store/sync/session/wait_for_completion.cpp index 3af36bf757e..5c658900303 100644 --- a/test/object-store/sync/session/wait_for_completion.cpp +++ b/test/object-store/sync/session/wait_for_completion.cpp @@ -98,16 +98,15 @@ TEST_CASE("SyncSession: wait_for_download_completion() API", "[sync]") { std::shared_ptr session = sync_session(user, "/async-wait-download-4", [&](auto, auto) { ++error_count; }); - std::error_code code = - std::error_code{static_cast(ProtocolError::bad_syntax), realm::sync::protocol_error_category()}; + Status err_status(ErrorCodes::SyncProtocolInvariantFailed, "Not a real error message"); // Register the download-completion notification session->wait_for_download_completion([&](Status status) { - REQUIRE(status.get_std_error_code() == code); + REQUIRE(status == err_status); handler_called = true; }); REQUIRE(handler_called == false); // Now trigger an error - sync::SessionErrorInfo err{code, "Not a real error message", false}; + sync::SessionErrorInfo err{err_status, false, ProtocolError::bad_syntax}; err.server_requests_action = sync::ProtocolErrorInfo::Action::ProtocolViolation; SyncSession::OnlyForTesting::handle_error(*session, std::move(err)); EventLoop::main().run_until([&] { diff --git a/test/sync_fixtures.hpp b/test/sync_fixtures.hpp index 31a2b800e0e..c6ed5f6123d 100644 --- a/test/sync_fixtures.hpp +++ b/test/sync_fixtures.hpp @@ -581,7 +581,7 @@ class MultiClientServerFixture { } } - using ErrorHandler = void(std::error_code ec, bool is_fatal, const std::string& detailed_message); + using ErrorHandler = void(Status status, bool is_fatal); // Set an error handler to be used for all sessions of the specified client // (\a handler will be copied for each session). Must be called before @@ -593,10 +593,8 @@ class MultiClientServerFixture { if (state != ConnectionState::disconnected) return; REALM_ASSERT(error_info); - std::error_code ec = error_info->error_code; bool is_fatal = error_info->is_fatal(); - const std::string& detailed_message = error_info->message; - handler(ec, is_fatal, detailed_message); + handler(error_info->status, is_fatal); }; m_connection_state_change_listeners[client_index] = std::move(handler_2); } @@ -715,8 +713,7 @@ class MultiClientServerFixture { return; REALM_ASSERT(error); unit_test::TestContext& test_context = m_test_context; - test_context.logger->error("Client disconnect: %1: %2 (is_fatal=%3)", error->error_code, - error->message, error->is_fatal()); + test_context.logger->error("Client disconnect: %1 (is_fatal=%3)", error->status, error->is_fatal()); bool client_error_occurred = true; CHECK_NOT(client_error_occurred); stop(); @@ -1081,10 +1078,8 @@ inline void RealmFixture::setup_error_handler(util::UniqueFunction if (state != ConnectionState::disconnected) return; REALM_ASSERT(error_info); - std::error_code ec = error_info->error_code; bool is_fatal = error_info->is_fatal(); - const std::string& detailed_message = error_info->message; - handler(ec, is_fatal, detailed_message); + handler(error_info->status, is_fatal); }; m_session.set_connection_state_change_listener(std::move(listener)); } diff --git a/test/test_client_reset.cpp b/test/test_client_reset.cpp index cc39933150b..08c171f2f2c 100644 --- a/test/test_client_reset.cpp +++ b/test/test_client_reset.cpp @@ -152,8 +152,8 @@ TEST(ClientReset_NoLocalChanges) if (state != ConnectionState::disconnected) return; REALM_ASSERT(error_info); - std::error_code ec = error_info->error_code; - CHECK_EQUAL(ec, sync::ProtocolError::bad_server_version); + CHECK_EQUAL(error_info->status, ErrorCodes::SyncClientResetRequired); + CHECK_EQUAL(error_info->to_protocol_error(), ProtocolError::bad_server_version); bowl.add_stone(); }; @@ -601,8 +601,8 @@ TEST(ClientReset_ThreeClients) if (state != ConnectionState::disconnected) return; REALM_ASSERT(error_info); - std::error_code ec = error_info->error_code; - CHECK_EQUAL(ec, sync::ProtocolError::bad_server_version); + CHECK_EQUAL(error_info->status, ErrorCodes::SyncClientResetRequired); + CHECK_EQUAL(error_info->to_protocol_error(), ProtocolError::bad_server_version); bowl.add_stone(); }; @@ -775,8 +775,7 @@ TEST(ClientReset_DoNotRecoverSchema) if (state != ConnectionState::disconnected) return; REALM_ASSERT(error_info); - std::error_code ec = error_info->error_code; - CHECK_EQUAL(ec, sync::Client::Error::auto_client_reset_failure); + CHECK_EQUAL(error_info->status, ErrorCodes::AutoClientResetFailed); bowl.add_stone(); }); session.bind(); diff --git a/test/test_sync.cpp b/test/test_sync.cpp index 465cb0b566c..6dfa5fba48f 100644 --- a/test/test_sync.cpp +++ b/test/test_sync.cpp @@ -146,9 +146,8 @@ TEST(Sync_BadVirtualPath) if (state != ConnectionState::disconnected) return; REALM_ASSERT(error_info); - std::error_code ec = error_info->error_code; bool is_fatal = error_info->is_fatal(); - CHECK_EQUAL(sync::ProtocolError::illegal_realm_path, ec); + CHECK_EQUAL(error_info->status, ErrorCodes::BadSyncPartitionValue); CHECK(is_fatal); ++nerrors; if (nerrors == 3) @@ -580,9 +579,7 @@ TEST(Sync_TokenWithoutExpirationAllowed) if (state != ConnectionState::disconnected) return; REALM_ASSERT(error_info); - std::error_code ec = error_info->error_code; - CHECK(ec == sync::ProtocolError::token_expired || ec == sync::ProtocolError::bad_authentication || - ec == sync::ProtocolError::permission_denied); + CHECK_EQUAL(error_info->status, ErrorCodes::SyncPermissionDenied); did_fail = true; fixture.stop(); }; @@ -611,7 +608,7 @@ TEST(Sync_TokenWithNullExpirationAllowed) TEST_DIR(dir); TEST_CLIENT_DB(db); ClientServerFixture fixture(dir, test_context); - auto error_handler = [&](std::error_code, bool, const std::string&) { + auto error_handler = [&](Status, bool) { did_fail = true; fixture.stop(); }; @@ -793,9 +790,7 @@ struct ExpectChangesetError { if (!error_info) return; REALM_ASSERT(error_info); - std::error_code ec = error_info->error_code; - CHECK_EQUAL(ec, sync::Client::Error::bad_changeset); - CHECK(ec.category() == client_error_category()); + CHECK_EQUAL(error_info->status, ErrorCodes::BadChangeset); CHECK(!error_info->is_fatal()); CHECK_EQUAL(error_info->message, "Bad changeset (DOWNLOAD): Failed to transform received changeset: Schema mismatch: " + @@ -1474,40 +1469,6 @@ TEST(Sync_Randomized) } -#ifdef REALM_DEBUG // Failure simulation only works in debug mode - -TEST(Sync_ReadFailureSimulation) -{ - TEST_DIR(server_dir); - TEST_CLIENT_DB(db); - - // Check that read failure simulation works on the client-side - { - bool client_side_read_did_fail = false; - { - ClientServerFixture fixture(server_dir, test_context); - fixture.set_client_side_error_rate(1, 1); // 100% chance of failure - auto error_handler = [&](std::error_code ec, bool is_fatal, const std::string&) { - CHECK_EQUAL(_impl::SimulatedFailure::sync_client__read_head, ec); - CHECK_NOT(is_fatal); - client_side_read_did_fail = true; - fixture.stop(); - }; - fixture.set_client_side_error_handler(error_handler); - Session session = fixture.make_bound_session(db, "/test"); - fixture.start(); - session.wait_for_download_complete_or_client_stopped(); - } - CHECK(client_side_read_did_fail); - } - - // FIXME: Figure out a way to check that read failure simulation works on - // the server-side -} - -#endif // REALM_DEBUG - - TEST(Sync_FailingReadsOnClientSide) { TEST_CLIENT_DB(db_1); @@ -1517,8 +1478,8 @@ TEST(Sync_FailingReadsOnClientSide) TEST_DIR(dir); ClientServerFixture fixture{dir, test_context}; fixture.set_client_side_error_rate(5, 100); // 5% chance of failure - auto error_handler = [&](std::error_code ec, bool, const std::string&) { - if (CHECK_EQUAL(_impl::SimulatedFailure::sync_client__read_head, ec)) + auto error_handler = [&](Status status, bool) { + if (CHECK_EQUAL(status, ErrorCodes::ConnectionClosed)) fixture.cancel_reconnect_delay(); }; fixture.set_client_side_error_handler(error_handler); @@ -1656,8 +1617,8 @@ TEST(Sync_ErrorAfterServerRestore_BadClientFileIdent) bool did_fail = false; { ClientServerFixture fixture(server_dir, test_context); - auto error_handler = [&](std::error_code ec, bool is_fatal, const std::string&) { - CHECK_EQUAL(ProtocolError::bad_server_version, ec); + auto error_handler = [&](Status status, bool is_fatal) { + CHECK_EQUAL(status, ErrorCodes::SyncClientResetRequired); CHECK(is_fatal); did_fail = true; fixture.stop(); @@ -1866,8 +1827,8 @@ TEST(Sync_ErrorAfterServerRestore_BadServerVersion) bool did_fail = false; { ClientServerFixture fixture(server_dir, test_context); - auto error_handler = [&](std::error_code ec, bool is_fatal, const std::string&) { - CHECK_EQUAL(ProtocolError::bad_server_version, ec); + auto error_handler = [&](Status status, bool is_fatal) { + CHECK_EQUAL(status, ErrorCodes::SyncClientResetRequired); CHECK(is_fatal); did_fail = true; fixture.stop(); @@ -1944,8 +1905,8 @@ TEST(Sync_ErrorAfterServerRestore_BadClientVersion) bool did_fail = false; { ClientServerFixture fixture(server_dir, test_context); - auto error_handler = [&](std::error_code ec, bool is_fatal, const std::string&) { - CHECK_EQUAL(ProtocolError::bad_client_version, ec); + auto error_handler = [&](Status status, bool is_fatal) { + CHECK_EQUAL(status, ErrorCodes::SyncClientResetRequired); CHECK(is_fatal); did_fail = true; fixture.stop(); @@ -2011,8 +1972,8 @@ TEST(Sync_ErrorAfterServerRestore_BadClientFileIdentSalt) bool did_fail = false; { ClientServerFixture fixture(server_dir, test_context); - auto error_handler = [&](std::error_code ec, bool is_fatal, const std::string&) { - CHECK_EQUAL(ProtocolError::diverging_histories, ec); + auto error_handler = [&](Status status, bool is_fatal) { + CHECK_EQUAL(status, ErrorCodes::SyncClientResetRequired); CHECK(is_fatal); did_fail = true; fixture.stop(); @@ -2095,8 +2056,8 @@ TEST(Sync_ErrorAfterServerRestore_BadServerVersionSalt) bool did_fail = false; { ClientServerFixture fixture(server_dir, test_context); - auto error_handler = [&](std::error_code ec, bool is_fatal, const std::string&) { - CHECK_EQUAL(ProtocolError::diverging_histories, ec); + auto error_handler = [&](Status status, bool is_fatal) { + CHECK_EQUAL(status, ErrorCodes::SyncClientResetRequired); CHECK(is_fatal); did_fail = true; fixture.stop(); @@ -2280,8 +2241,8 @@ TEST_IF(Sync_ReadOnlyClient, false) TEST_DIR(server_dir); MultiClientServerFixture fixture(2, 1, server_dir, test_context); bool did_get_permission_denied = false; - fixture.set_client_side_error_handler(1, [&](std::error_code ec, bool, const std::string&) { - CHECK_EQUAL(ProtocolError::permission_denied, ec); + fixture.set_client_side_error_handler(1, [&](Status status, bool) { + CHECK_EQUAL(status, ErrorCodes::SyncPermissionDenied); did_get_permission_denied = true; fixture.get_client(1).shutdown(); }); @@ -2674,8 +2635,8 @@ TEST(Sync_Permissions) TEST_DIR(server_dir); ClientServerFixture fixture{server_dir, test_context}; - fixture.set_client_side_error_handler([&](std::error_code, bool, const std::string& message) { - CHECK_EQUAL("", message); + fixture.set_client_side_error_handler([&](Status status, bool) { + CHECK_EQUAL("", status.reason()); did_see_error_for_valid = true; }); fixture.start(); @@ -2744,8 +2705,8 @@ TEST(Sync_SSL_Certificate_2) session_config.verify_servers_ssl_certificate = true; session_config.ssl_trust_certificate_path = ca_dir + "dns-chain.crt.pem"; - auto error_handler = [&](std::error_code ec, bool, const std::string&) { - CHECK_EQUAL(ec, Client::Error::ssl_server_cert_rejected); + auto error_handler = [&](Status status, bool) { + CHECK_EQUAL(status, ErrorCodes::TlsHandshakeFailed); did_fail = true; fixture.stop(); }; @@ -2895,8 +2856,8 @@ TEST(Sync_SSL_Certificate_Verify_Callback_2) ClientServerFixture fixture{server_dir, test_context, config}; - auto error_handler = [&](std::error_code ec, bool, const std::string&) { - CHECK_EQUAL(ec, Client::Error::ssl_server_cert_rejected); + auto error_handler = [&](Status status, bool) { + CHECK_EQUAL(status, ErrorCodes::TlsHandshakeFailed); did_fail = true; fixture.stop(); }; @@ -3892,7 +3853,7 @@ TEST(Sync_CancelReconnectDelay) BowlOfStonesSemaphore bowl; auto handler = [&](const SessionErrorInfo& info) { - if (CHECK_EQUAL(info.error_code, ProtocolError::connection_closed)) + if (CHECK_EQUAL(info.status, ErrorCodes::ConnectionClosed)) bowl.add_stone(); }; Session session = fixture.make_session(db, "/test"); @@ -3914,7 +3875,7 @@ TEST(Sync_CancelReconnectDelay) BowlOfStonesSemaphore bowl; auto handler = [&](const SessionErrorInfo& info) { - if (CHECK_EQUAL(info.error_code, ProtocolError::connection_closed)) + if (CHECK_EQUAL(info.status, ErrorCodes::ConnectionClosed)) bowl.add_stone(); }; Session session = fixture.make_session(db, "/test"); @@ -3937,7 +3898,7 @@ TEST(Sync_CancelReconnectDelay) { BowlOfStonesSemaphore bowl; auto handler = [&](const SessionErrorInfo& info) { - if (CHECK_EQUAL(info.error_code, ProtocolError::connection_closed)) + if (CHECK_EQUAL(info.status, ErrorCodes::ConnectionClosed)) bowl.add_stone(); }; Session session = fixture.make_session(db, "/test"); @@ -3973,7 +3934,7 @@ TEST(Sync_CancelReconnectDelay) BowlOfStonesSemaphore bowl; auto handler = [&](const SessionErrorInfo& info) { - if (CHECK_EQUAL(info.error_code, ProtocolError::illegal_realm_path)) + if (CHECK_EQUAL(info.status, ErrorCodes::BadSyncPartitionValue)) bowl.add_stone(); }; Session session = fixture.make_session(db, "/.."); @@ -3996,7 +3957,7 @@ TEST(Sync_CancelReconnectDelay) BowlOfStonesSemaphore bowl; auto handler = [&](const SessionErrorInfo& info) { - if (CHECK_EQUAL(info.error_code, ProtocolError::illegal_realm_path)) + if (CHECK_EQUAL(info.status, ErrorCodes::BadSyncPartitionValue)) bowl.add_stone(); }; Session session = fixture.make_session(db, "/.."); @@ -4501,8 +4462,9 @@ TEST(Sync_PingTimesOut) config.client_pong_timeout = 0; // time out immediately ClientServerFixture fixture(dir, test_context, std::move(config)); - auto error_handler = [&](std::error_code ec, bool, const std::string&) { - CHECK_EQUAL(Client::Error::pong_timeout, ec); + auto error_handler = [&](Status status, bool) { + CHECK_EQUAL(status, ErrorCodes::ConnectionClosed); + CHECK_EQUAL(status.reason(), "Timed out waiting for PONG message"); did_fail = true; fixture.stop(); }; @@ -4529,9 +4491,11 @@ TEST(Sync_ReconnectAfterPingTimeout) ClientServerFixture fixture(dir, test_context, std::move(config)); BowlOfStonesSemaphore bowl; - auto error_handler = [&](std::error_code ec, bool, const std::string&) { - if (CHECK_EQUAL(Client::Error::pong_timeout, ec)) + auto error_handler = [&](Status status, bool) { + if (CHECK_EQUAL(status, ErrorCodes::ConnectionClosed)) { + CHECK_EQUAL(status.reason(), "Timed out waiting for PONG message"); bowl.add_stone(); + } }; fixture.set_client_side_error_handler(std::move(error_handler)); fixture.start(); @@ -4553,8 +4517,9 @@ TEST(Sync_UrgentPingIsSent) ClientServerFixture fixture(dir, test_context, std::move(config)); - auto error_handler = [&](std::error_code ec, bool, const std::string&) { - CHECK_EQUAL(Client::Error::pong_timeout, ec); + auto error_handler = [&](Status status, bool) { + CHECK_EQUAL(status, ErrorCodes::ConnectionClosed); + CHECK_EQUAL(status.reason(), "Timed out waiting for PONG message"); did_fail = true; fixture.stop(); }; @@ -4582,9 +4547,8 @@ TEST(Sync_ServerDiscardDeadConnections) ClientServerFixture fixture(dir, test_context, std::move(config)); BowlOfStonesSemaphore bowl; - auto error_handler = [&](std::error_code ec, bool, const std::string&) { - bool valid_error = (ec == sync::websocket::WebSocketError::websocket_read_error); - CHECK(valid_error); + auto error_handler = [&](Status status, bool) { + CHECK_EQUAL(status, ErrorCodes::ConnectionClosed); bowl.add_stone(); }; fixture.set_client_side_error_handler(std::move(error_handler)); @@ -5178,11 +5142,10 @@ TEST_IF(Sync_SSL_Certificates, false) auto listener = [&](ConnectionState state, const util::Optional& error_info) { if (state == ConnectionState::disconnected) { CHECK(error_info); - client_logger->debug( - "State change: disconnected, error_code = %1, is_fatal = %2, detailed_message = %3", - error_info->error_code, error_info->is_fatal(), error_info->message); + client_logger->debug("State change: disconnected, error_code = %1, is_fatal = %2", error_info->status, + error_info->is_fatal()); // We expect to get through the SSL handshake but will hit an error due to the wrong token. - CHECK_NOT_EQUAL(error_info->error_code, Client::Error::ssl_server_cert_rejected); + CHECK_NOT_EQUAL(error_info->status, ErrorCodes::TlsHandshakeFailed); client.shutdown(); } }; @@ -5256,10 +5219,8 @@ TEST(Sync_BadChangeset) if (state != ConnectionState::disconnected) return; REALM_ASSERT(error_info); - std::error_code ec = error_info->error_code; - bool is_fatal = error_info->is_fatal(); - CHECK_EQUAL(sync::ProtocolError::bad_changeset, ec); - CHECK(is_fatal); + CHECK_EQUAL(error_info->status, ErrorCodes::BadChangeset); + CHECK(error_info->is_fatal()); did_fail = true; fixture.stop(); }; diff --git a/test/test_util_websocket.cpp b/test/test_util_websocket.cpp index 7f2a5ad4e9d..752340c9d5c 100644 --- a/test/test_util_websocket.cpp +++ b/test/test_util_websocket.cpp @@ -171,7 +171,7 @@ class WSConfig : public websocket::Config { std::vector text_messages; std::vector binary_messages; - std::vector> close_messages; + std::vector> close_messages; std::vector ping_messages; std::vector pong_messages; @@ -244,7 +244,8 @@ class WSConfig : public websocket::Config { return true; } - bool websocket_close_message_received(std::error_code error_code, StringData error_message) override + bool websocket_close_message_received(websocket::WebSocketError error_code, + std::string_view error_message) override { close_messages.push_back(std::make_pair(error_code, std::string{error_message})); return true; @@ -389,7 +390,7 @@ TEST(WebSocket_Messages) "close message", 15, handler_no_op); CHECK_EQUAL(config_1.close_messages.size(), 1); - CHECK_EQUAL(config_1.close_messages[0].first.value(), 1000); + CHECK_EQUAL(static_cast(config_1.close_messages[0].first), 1000); CHECK_EQUAL(config_1.close_messages[0].second, "close message"); std::vector message_sizes{1, 2, 100, 125, 126, 127, 128, 200, 1000, 65000, 65535, 65536, 100000, 1000000}; From 66eee631dd70ecd5769fe4c0b075e1b3fa68dd7a Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Wed, 12 Jul 2023 18:26:35 -0400 Subject: [PATCH 02/30] remove more std::error_codes --- src/realm/error_codes.cpp | 8 - src/realm/error_codes.h | 38 ---- src/realm/error_codes.hpp | 4 - src/realm/exceptions.hpp | 25 ++- src/realm/impl/simulated_failure.hpp | 32 +++- .../object-store/c_api/socket_provider.cpp | 5 +- src/realm/object-store/c_api/sync.cpp | 16 +- src/realm/status.cpp | 10 +- src/realm/status.hpp | 42 ++++- src/realm/sync/client.cpp | 173 ----------------- src/realm/sync/client.hpp | 2 - src/realm/sync/client_base.hpp | 56 ------ src/realm/sync/config.hpp | 2 - src/realm/sync/network/websocket.cpp | 176 +++++++----------- src/realm/sync/network/websocket.hpp | 11 -- src/realm/sync/noinst/client_impl_base.cpp | 15 +- src/realm/sync/noinst/client_impl_base.hpp | 1 - test/object-store/c_api/c_api.cpp | 22 +-- test/test_sync.cpp | 48 ++++- 19 files changed, 212 insertions(+), 474 deletions(-) diff --git a/src/realm/error_codes.cpp b/src/realm/error_codes.cpp index 6b28b261e17..88445b35929 100644 --- a/src/realm/error_codes.cpp +++ b/src/realm/error_codes.cpp @@ -224,11 +224,6 @@ ErrorCategory ErrorCodes::error_categories(Error code) .set(ErrorCategory::app_error) .set(ErrorCategory::service_error); - case WebSocketResolveFailedError: - case WebSocketConnectionClosedClientError: - case WebSocketConnectionClosedServerError: - return ErrorCategory().set(ErrorCategory::runtime_error).set(ErrorCategory::websocket_error); - case UnknownError: break; } @@ -398,9 +393,6 @@ static const MapElem string_to_error_code[] = { {"ValueAlreadyExists", ErrorCodes::ValueAlreadyExists}, {"ValueDuplicateName", ErrorCodes::ValueDuplicateName}, {"ValueNotFound", ErrorCodes::ValueNotFound}, - {"WebSocketConnectionClosedClientError", ErrorCodes::WebSocketConnectionClosedClientError}, - {"WebSocketConnectionClosedServerError", ErrorCodes::WebSocketConnectionClosedServerError}, - {"WebSocketResolveFailedError", ErrorCodes::WebSocketResolveFailedError}, {"WrongSyncType", ErrorCodes::WrongSyncType}, {"WrongThread", ErrorCodes::WrongThread}, {"WrongTransactionState", ErrorCodes::WrongTransactionState}, diff --git a/src/realm/error_codes.h b/src/realm/error_codes.h index 6eb2b2b6ae7..01d537f42ce 100644 --- a/src/realm/error_codes.h +++ b/src/realm/error_codes.h @@ -201,48 +201,10 @@ typedef enum realm_errno { RLM_ERR_USERPASS_TOKEN_INVALID = 4353, RLM_ERR_INVALID_SERVER_RESPONSE = 4354, - RLM_ERR_WEBSOCKET_RESOLVE_FAILED_ERROR = 4400, - RLM_ERR_WEBSOCKET_CONNECTION_CLOSED_CLIENT_ERROR = 4401, - RLM_ERR_WEBSOCKET_CONNECTION_CLOSED_SERVER_ERROR = 4402, - RLM_ERR_CALLBACK = 1000000, /**< A user-provided callback failed. */ RLM_ERR_UNKNOWN = 2000000 /* Should not be used in code */ } realm_errno_e; -typedef enum realm_sync_errno_client { - RLM_SYNC_ERR_CLIENT_CONNECTION_CLOSED = 100, - RLM_SYNC_ERR_CLIENT_UNKNOWN_MESSAGE = 101, - RLM_SYNC_ERR_CLIENT_BAD_SYNTAX = 102, - RLM_SYNC_ERR_CLIENT_LIMITS_EXCEEDED = 103, - RLM_SYNC_ERR_CLIENT_BAD_SESSION_IDENT = 104, - RLM_SYNC_ERR_CLIENT_BAD_MESSAGE_ORDER = 105, - RLM_SYNC_ERR_CLIENT_BAD_CLIENT_FILE_IDENT = 106, - RLM_SYNC_ERR_CLIENT_BAD_PROGRESS = 107, - RLM_SYNC_ERR_CLIENT_BAD_CHANGESET_HEADER_SYNTAX = 108, - RLM_SYNC_ERR_CLIENT_BAD_CHANGESET_SIZE = 109, - RLM_SYNC_ERR_CLIENT_BAD_ORIGIN_FILE_IDENT = 110, - RLM_SYNC_ERR_CLIENT_BAD_SERVER_VERSION = 111, - RLM_SYNC_ERR_CLIENT_BAD_CHANGESET = 112, - RLM_SYNC_ERR_CLIENT_BAD_REQUEST_IDENT = 113, - RLM_SYNC_ERR_CLIENT_BAD_ERROR_CODE = 114, - RLM_SYNC_ERR_CLIENT_BAD_COMPRESSION = 115, - RLM_SYNC_ERR_CLIENT_BAD_CLIENT_VERSION = 116, - RLM_SYNC_ERR_CLIENT_SSL_SERVER_CERT_REJECTED = 117, - RLM_SYNC_ERR_CLIENT_PONG_TIMEOUT = 118, - RLM_SYNC_ERR_CLIENT_BAD_CLIENT_FILE_IDENT_SALT = 119, - RLM_SYNC_ERR_CLIENT_BAD_FILE_IDENT = 120, - RLM_SYNC_ERR_CLIENT_CONNECT_TIMEOUT = 121, - RLM_SYNC_ERR_CLIENT_BAD_TIMESTAMP = 122, - RLM_SYNC_ERR_CLIENT_BAD_PROTOCOL_FROM_SERVER = 123, - RLM_SYNC_ERR_CLIENT_CLIENT_TOO_OLD_FOR_SERVER = 124, - RLM_SYNC_ERR_CLIENT_CLIENT_TOO_NEW_FOR_SERVER = 125, - RLM_SYNC_ERR_CLIENT_PROTOCOL_MISMATCH = 126, - RLM_SYNC_ERR_CLIENT_BAD_STATE_MESSAGE = 127, - RLM_SYNC_ERR_CLIENT_MISSING_PROTOCOL_FEATURE = 128, - RLM_SYNC_ERR_CLIENT_HTTP_TUNNEL_FAILED = 131, - RLM_SYNC_ERR_CLIENT_AUTO_CLIENT_RESET_FAILURE = 132, -} realm_sync_errno_client_e; - typedef enum realm_sync_errno_connection { RLM_SYNC_ERR_CONNECTION_CONNECTION_CLOSED = 100, RLM_SYNC_ERR_CONNECTION_OTHER_ERROR = 101, diff --git a/src/realm/error_codes.hpp b/src/realm/error_codes.hpp index 69c3438f680..7c3ae4ea316 100644 --- a/src/realm/error_codes.hpp +++ b/src/realm/error_codes.hpp @@ -245,10 +245,6 @@ class ErrorCodes { UserpassTokenInvalid = RLM_ERR_USERPASS_TOKEN_INVALID, InvalidServerResponse = RLM_ERR_INVALID_SERVER_RESPONSE, - WebSocketResolveFailedError = RLM_ERR_WEBSOCKET_RESOLVE_FAILED_ERROR, - WebSocketConnectionClosedClientError = RLM_ERR_WEBSOCKET_CONNECTION_CLOSED_CLIENT_ERROR, - WebSocketConnectionClosedServerError = RLM_ERR_WEBSOCKET_CONNECTION_CLOSED_SERVER_ERROR, - CallbackFailed = RLM_ERR_CALLBACK, UnknownError = RLM_ERR_UNKNOWN, }; diff --git a/src/realm/exceptions.hpp b/src/realm/exceptions.hpp index f5dbeffa361..4aef8616fec 100644 --- a/src/realm/exceptions.hpp +++ b/src/realm/exceptions.hpp @@ -355,6 +355,23 @@ class FileAccessError : public RuntimeError { }; struct SystemError : RuntimeError { + class ExtraInfo : public Status::ExtraInfo { + public: + ExtraInfo(std::error_code ec) + : ec(ec) + { + } + + std::error_code ec; + }; + + static Status make_status(std::error_code err, std::string_view msg, bool msg_is_prefix) + { + return Status(ErrorCodes::SystemError, + msg_is_prefix ? util::format("%1: %2 (%3)", msg, err.message(), err.value()) : msg, + std::make_unique(err)); + } + SystemError(std::error_code err, std::string_view msg) : RuntimeError(make_status(err, msg, false)) { @@ -369,19 +386,13 @@ struct SystemError : RuntimeError { std::error_code get_system_error() const { - return to_status().get_std_error_code(); + return to_status().get_extra_info().ec; } const std::error_category& get_category() const { return get_system_error().category(); } - -private: - static Status make_status(std::error_code err, std::string_view msg, bool msg_is_prefix) - { - return Status(err, msg_is_prefix ? util::format("%1: %2 (%3)", msg, err.message(), err.value()) : msg); - } }; namespace query_parser { diff --git a/src/realm/impl/simulated_failure.hpp b/src/realm/impl/simulated_failure.hpp index 784f8c9fa0c..e983f47693f 100644 --- a/src/realm/impl/simulated_failure.hpp +++ b/src/realm/impl/simulated_failure.hpp @@ -24,6 +24,7 @@ #include #include +#include #ifdef REALM_DEBUG #define REALM_ENABLE_SIMULATED_FAILURE @@ -32,7 +33,7 @@ namespace realm { namespace _impl { -class SimulatedFailure : public SystemError { +class SimulatedFailure : public RuntimeError { public: enum FailureType { generic, @@ -45,6 +46,16 @@ class SimulatedFailure : public SystemError { _num_failure_types }; + class ExtraInfo : public Status::ExtraInfo { + public: + ExtraInfo(FailureType failure_type) + : failure_type(failure_type) + { + } + + FailureType failure_type; + }; + class OneShotPrimeGuard; class RandomPrimeGuard; @@ -65,6 +76,9 @@ class SimulatedFailure : public SystemError { /// not defined, this function always return false. static bool check_trigger(FailureType) noexcept; + static Status make_simulated_failure_status(FailureType failure_type, + ErrorCodes::Error error_code = ErrorCodes::RuntimeError); + /// Throws SimulatedFailure if check_trigger() returns true. The exception /// will be constructed with an error code equal to /// `make_error_code(failure_type)`. @@ -86,7 +100,7 @@ class SimulatedFailure : public SystemError { /// when turning this off. static void set_thread_local(bool); - SimulatedFailure(std::error_code); + SimulatedFailure(FailureType); private: #ifdef REALM_ENABLE_SIMULATED_FAILURE @@ -128,7 +142,8 @@ std::error_code make_error_code(SimulatedFailure::FailureType) noexcept; namespace std { -template<> struct is_error_code_enum { +template <> +struct is_error_code_enum { static const bool value = true; }; @@ -180,10 +195,15 @@ inline bool SimulatedFailure::check_trigger(FailureType failure_type) noexcept #endif } +inline Status SimulatedFailure::make_simulated_failure_status(FailureType failure_type, ErrorCodes::Error error_code) +{ + return Status(error_code, "SimulatedFailure", std::make_unique(failure_type)); +} + inline void SimulatedFailure::trigger(FailureType failure_type) { if (check_trigger(failure_type)) - throw SimulatedFailure(make_error_code(failure_type)); + throw SimulatedFailure(failure_type); } inline constexpr bool SimulatedFailure::is_enabled() @@ -204,8 +224,8 @@ inline void SimulatedFailure::set_thread_local(bool tl) #endif } -inline SimulatedFailure::SimulatedFailure(std::error_code ec) - : SystemError(ec, {}) +inline SimulatedFailure::SimulatedFailure(FailureType type) + : RuntimeError(Status{ErrorCodes::RuntimeError, "SimulatedFailure", std::make_unique(type)}) { } diff --git a/src/realm/object-store/c_api/socket_provider.cpp b/src/realm/object-store/c_api/socket_provider.cpp index b922af2c1f9..5a6bdc4f171 100644 --- a/src/realm/object-store/c_api/socket_provider.cpp +++ b/src/realm/object-store/c_api/socket_provider.cpp @@ -221,10 +221,11 @@ RLM_API realm_sync_socket_t* realm_sync_socket_new( RLM_API void realm_sync_socket_callback_complete(realm_sync_socket_callback* realm_callback, realm_web_socket_errno_e code, const char* reason) { - auto status = sync::websocket::WebSocketError(code); + auto close_code = sync::websocket::WebSocketError(code); + auto complete_status = code == realm_web_socket_errno_e::RLM_ERR_WEBSOCKET_OK ? Status::OK() - : Status{sync::websocket::make_error_code(status), reason}; + : Status{ErrorCodes::ConnectionClosed, reason}; (*(realm_callback->get()))(complete_status); realm_release(realm_callback); } diff --git a/src/realm/object-store/c_api/sync.cpp b/src/realm/object-store/c_api/sync.cpp index 5def4277de3..86fea7c0c21 100644 --- a/src/realm/object-store/c_api/sync.cpp +++ b/src/realm/object-store/c_api/sync.cpp @@ -128,10 +128,7 @@ realm_sync_error_code_t to_capi(const Status& status, std::string& message) auto error_code = status.get_std_error_code(); const std::error_category& category = error_code.category(); - if (category == realm::sync::client_error_category()) { - ret.category = RLM_SYNC_ERROR_CATEGORY_CLIENT; - } - else if (category == realm::sync::protocol_error_category()) { + if (category == realm::sync::protocol_error_category()) { if (realm::sync::is_session_level_error(realm::sync::ProtocolError(error_code.value()))) { ret.category = RLM_SYNC_ERROR_CATEGORY_SESSION; } @@ -142,9 +139,6 @@ realm_sync_error_code_t to_capi(const Status& status, std::string& message) else if (category == std::system_category() || category == realm::util::error::basic_system_error_category()) { ret.category = RLM_SYNC_ERROR_CATEGORY_SYSTEM; } - else if (category == realm::sync::websocket::websocket_error_category()) { - ret.category = RLM_SYNC_ERROR_CATEGORY_WEBSOCKET; - } else { ret.category = RLM_SYNC_ERROR_CATEGORY_UNKNOWN; } @@ -162,18 +156,12 @@ void sync_error_to_error_code(const realm_sync_error_code_t& sync_error_code, st { if (error_code_out) { const realm_sync_error_category_e category = sync_error_code.category; - if (category == RLM_SYNC_ERROR_CATEGORY_CLIENT) { - error_code_out->assign(sync_error_code.value, realm::sync::client_error_category()); - } - else if (category == RLM_SYNC_ERROR_CATEGORY_SESSION || category == RLM_SYNC_ERROR_CATEGORY_CONNECTION) { + if (category == RLM_SYNC_ERROR_CATEGORY_SESSION || category == RLM_SYNC_ERROR_CATEGORY_CONNECTION) { error_code_out->assign(sync_error_code.value, realm::sync::protocol_error_category()); } else if (category == RLM_SYNC_ERROR_CATEGORY_SYSTEM) { error_code_out->assign(sync_error_code.value, std::system_category()); } - else if (category == RLM_SYNC_ERROR_CATEGORY_WEBSOCKET) { - error_code_out->assign(sync_error_code.value, realm::sync::websocket::websocket_error_category()); - } else if (category == RLM_SYNC_ERROR_CATEGORY_UNKNOWN) { error_code_out->assign(sync_error_code.value, realm::util::error::basic_system_error_category()); } diff --git a/src/realm/status.cpp b/src/realm/status.cpp index 0f097e254d8..8aa4e9f0e5d 100644 --- a/src/realm/status.cpp +++ b/src/realm/status.cpp @@ -22,20 +22,22 @@ namespace realm { -Status::ErrorInfo::ErrorInfo(ErrorCodes::Error code, std::string_view reason) +Status::ErrorInfo::ErrorInfo(ErrorCodes::Error code, std::string&& reason, std::unique_ptr&& extra_info) : m_refs(0) , m_code(code) - , m_reason(reason) + , m_reason(std::move(reason)) + , m_extra_info(std::move(extra_info)) { } -util::bind_ptr Status::ErrorInfo::create(ErrorCodes::Error code, std::string_view reason) +util::bind_ptr Status::ErrorInfo::create(ErrorCodes::Error code, std::string&& reason, + std::unique_ptr&& extra_info) { // OK status should be created by calling Status::OK() - which is a special case that doesn't allocate // anything. REALM_ASSERT(code != ErrorCodes::OK); - return util::bind_ptr(new ErrorInfo(code, std::move(reason))); + return util::bind_ptr(new ErrorInfo(code, std::move(reason), std::move(extra_info))); } std::ostream& operator<<(std::ostream& out, const Status& val) diff --git a/src/realm/status.hpp b/src/realm/status.hpp index 015a748197a..46fcb5113a0 100644 --- a/src/realm/status.hpp +++ b/src/realm/status.hpp @@ -31,6 +31,11 @@ namespace realm { class REALM_NODISCARD Status { public: + class ExtraInfo { + public: + virtual ~ExtraInfo() = default; + }; + /* * This is the best way to construct a Status that represents a non-error condition. */ @@ -40,8 +45,13 @@ class REALM_NODISCARD Status { * You can construct a Status from anything that can construct a std::string_view. */ template , int> = 0> - Status(ErrorCodes::Error code, Reason&& reason) - : m_error(ErrorInfo::create(code, std::string_view{reason})) + Status(ErrorCodes::Error code, Reason&& reason, std::unique_ptr extra_info = nullptr) + : m_error(ErrorInfo::create(code, std::string{std::string_view{reason}}, std::move(extra_info))) + { + } + + Status(ErrorCodes::Error code, std::string&& reason, std::unique_ptr extra_info = nullptr) + : m_error(ErrorInfo::create(code, std::move(reason), std::move(extra_info))) { } @@ -49,7 +59,7 @@ class REALM_NODISCARD Status { Status(std::error_code code, Reason&& reason) { if (code) { - m_error = ErrorInfo::create(ErrorCodes::SystemError, std::string_view{reason}); + m_error = ErrorInfo::create(ErrorCodes::SystemError, std::string{std::string_view{reason}}, nullptr); m_error->m_std_error_code = code; } } @@ -84,6 +94,25 @@ class REALM_NODISCARD Status { */ void ignore() const noexcept {} + template + bool has_extra_info() const noexcept + { + if (!m_error || !m_error->m_extra_info) { + return false; + } + + return dynamic_cast(m_error->m_extra_info.get()) != nullptr; + } + + template + const T& get_extra_info() const noexcept + { + REALM_ASSERT(m_error); + REALM_ASSERT(m_error->m_extra_info); + + return *dynamic_cast(m_error->m_extra_info.get()); + } + private: friend struct SystemError; void set_std_error_code(std::error_code code) @@ -101,7 +130,10 @@ class REALM_NODISCARD Status { // std::error_code migrated to the unified exception system std::error_code m_std_error_code; - static util::bind_ptr create(ErrorCodes::Error code, std::string_view reason); + std::unique_ptr m_extra_info; + + static util::bind_ptr create(ErrorCodes::Error code, std::string&& reason, + std::unique_ptr&& extra_info); protected: template @@ -120,7 +152,7 @@ class REALM_NODISCARD Status { } private: - ErrorInfo(ErrorCodes::Error code, std::string_view reason); + ErrorInfo(ErrorCodes::Error code, std::string&& reason, std::unique_ptr&& extra_info); }; util::bind_ptr m_error = {}; diff --git a/src/realm/sync/client.cpp b/src/realm/sync/client.cpp index ec0e863fbd5..0ee02236e66 100644 --- a/src/realm/sync/client.cpp +++ b/src/realm/sync/client.cpp @@ -27,7 +27,6 @@ namespace sync { namespace { using namespace realm::util; - // clang-format off using SessionImpl = ClientImpl::Session; using SyncTransactReporter = ClientHistory::SyncTransactReporter; @@ -40,96 +39,6 @@ using connection_ident_type = std::int_fast64_t; using ProxyConfig = SyncConfig::ProxyConfig; // clang-format on - -const char* get_error_message(ClientError error_code) -{ - switch (error_code) { - case ClientError::connection_closed: - return "Connection closed (no error)"; - case ClientError::unknown_message: - return "Unknown type of input message"; - case ClientError::bad_syntax: - return "Bad syntax in input message head"; - case ClientError::limits_exceeded: - return "Limits exceeded in input message"; - case ClientError::bad_session_ident: - return "Bad session identifier in input message"; - case ClientError::bad_message_order: - return "Bad input message order"; - case ClientError::bad_client_file_ident: - return "Bad client file identifier (IDENT)"; - case ClientError::bad_progress: - return "Bad progress information (DOWNLOAD)"; - case ClientError::bad_changeset_header_syntax: - return "Bad progress information (DOWNLOAD)"; - case ClientError::bad_changeset_size: - return "Bad changeset size in changeset header (DOWNLOAD)"; - case ClientError::bad_origin_file_ident: - return "Bad origin file identifier in changeset header (DOWNLOAD)"; - case ClientError::bad_server_version: - return "Bad server version in changeset header (DOWNLOAD)"; - case ClientError::bad_changeset: - return "Bad changeset (DOWNLOAD)"; - case ClientError::bad_request_ident: - return "Bad request identifier (MARK)"; - case ClientError::bad_error_code: - return "Bad error code (ERROR)"; - case ClientError::bad_compression: - return "Bad compression (DOWNLOAD)"; - case ClientError::bad_client_version: - return "Bad last integrated client version in changeset header (DOWNLOAD)"; - case ClientError::ssl_server_cert_rejected: - return "SSL server certificate rejected"; - case ClientError::pong_timeout: - return "Timeout on reception of PONG respone message"; - case ClientError::bad_client_file_ident_salt: - return "Bad client file identifier salt (IDENT)"; - case ClientError::bad_file_ident: - return "Bad file identifier (ALLOC)"; - case ClientError::connect_timeout: - return "Sync connection was not fully established in time"; - case ClientError::bad_timestamp: - return "Bad timestamp (PONG)"; - case ClientError::bad_protocol_from_server: - return "Bad or missing protocol version information from server"; - case ClientError::client_too_old_for_server: - return "Protocol version negotiation failed: Client is too old for server"; - case ClientError::client_too_new_for_server: - return "Protocol version negotiation failed: Client is too new for server"; - case ClientError::protocol_mismatch: - return ("Protocol version negotiation failed: No version supported by both " - "client and server"); - case ClientError::bad_state_message: - return "Bad state message (STATE)"; - case ClientError::missing_protocol_feature: - return "Requested feature missing in negotiated protocol version"; - case ClientError::http_tunnel_failed: - return "Failure to establish HTTP tunnel with configured proxy"; - case ClientError::auto_client_reset_failure: - return "Automatic recovery from client reset failed"; - } - return nullptr; -} - - -class ErrorCategoryImpl : public std::error_category { -public: - const char* name() const noexcept override final - { - return "realm::sync::ClientError"; - } - std::string message(int error_code) const override final - { - const char* msg = get_error_message(ClientError(error_code)); - if (!msg) - msg = "Unknown error"; - std::string msg_2{msg}; // Throws (copy) - return msg_2; - } -}; - -ErrorCategoryImpl g_error_category; - } // unnamed namespace @@ -2233,88 +2142,6 @@ std::string Session::get_appservices_connection_id() return m_impl->get_appservices_connection_id(); } -const std::error_category& client_error_category() noexcept -{ - return g_error_category; -} - -std::error_code make_error_code(ClientError error_code) noexcept -{ - return std::error_code{int(error_code), g_error_category}; -} - -ProtocolError client_error_to_protocol_error(ClientError error_code) -{ - switch (error_code) { - case ClientError::bad_changeset: - return ProtocolError::bad_changeset; - case ClientError::bad_progress: - return ProtocolError::bad_progress; - case ClientError::bad_changeset_size: - return ProtocolError::bad_changeset_size; - case ClientError::connection_closed: - return ProtocolError::connection_closed; - case ClientError::unknown_message: - return ProtocolError::unknown_message; - case ClientError::bad_syntax: - return ProtocolError::bad_syntax; - case ClientError::limits_exceeded: - return ProtocolError::limits_exceeded; - case ClientError::bad_session_ident: - return ProtocolError::bad_session_ident; - case ClientError::bad_message_order: - return ProtocolError::bad_message_order; - case ClientError::bad_client_file_ident: - return ProtocolError::bad_client_file_ident; - case ClientError::bad_changeset_header_syntax: - return ProtocolError::bad_changeset_header_syntax; - case ClientError::bad_origin_file_ident: - return ProtocolError::bad_origin_file_ident; - case ClientError::bad_server_version: - return ProtocolError::bad_server_version; - case ClientError::bad_client_version: - return ProtocolError::bad_client_version; - - case ClientError::bad_error_code: - [[fallthrough]]; - case ClientError::bad_request_ident: - [[fallthrough]]; - case ClientError::bad_compression: - [[fallthrough]]; - case ClientError::ssl_server_cert_rejected: - [[fallthrough]]; - case ClientError::bad_client_file_ident_salt: - [[fallthrough]]; - case ClientError::bad_file_ident: - [[fallthrough]]; - case ClientError::connect_timeout: - [[fallthrough]]; - case ClientError::bad_timestamp: - [[fallthrough]]; - case ClientError::bad_protocol_from_server: - [[fallthrough]]; - case ClientError::client_too_old_for_server: - [[fallthrough]]; - case ClientError::client_too_new_for_server: - [[fallthrough]]; - case ClientError::protocol_mismatch: - [[fallthrough]]; - case ClientError::missing_protocol_feature: - [[fallthrough]]; - case ClientError::http_tunnel_failed: - return ProtocolError::other_error; - - case ClientError::auto_client_reset_failure: - [[fallthrough]]; - case ClientError::pong_timeout: - [[fallthrough]]; - case ClientError::bad_state_message: - return ProtocolError::other_session_error; - } - - return ProtocolError::other_error; -} - std::ostream& operator<<(std::ostream& os, ProxyConfig::Type proxyType) { switch (proxyType) { diff --git a/src/realm/sync/client.hpp b/src/realm/sync/client.hpp index 040bd22ea8b..0e246b93eda 100644 --- a/src/realm/sync/client.hpp +++ b/src/realm/sync/client.hpp @@ -23,8 +23,6 @@ class Client { public: using port_type = sync::port_type; - using Error = ClientError; - static constexpr milliseconds_type default_connect_timeout = sync::default_connect_timeout; static constexpr milliseconds_type default_connection_linger_time = sync::default_connection_linger_time; static constexpr milliseconds_type default_ping_keepalive_period = sync::default_ping_keepalive_period; diff --git a/src/realm/sync/client_base.hpp b/src/realm/sync/client_base.hpp index b190cd549fb..4a9983645d9 100644 --- a/src/realm/sync/client_base.hpp +++ b/src/realm/sync/client_base.hpp @@ -67,64 +67,8 @@ struct ClientReset { util::UniqueFunction notify_after_client_reset; }; -/// \brief Protocol errors discovered by the client. -/// -/// These errors will terminate the network connection (disconnect all sessions -/// associated with the affected connection), and the error will be reported to -/// the application via the connection state change listeners of the affected -/// sessions. -enum class ClientError { - // clang-format off - connection_closed = RLM_SYNC_ERR_CLIENT_CONNECTION_CLOSED , ///< Connection closed (no error) - unknown_message = RLM_SYNC_ERR_CLIENT_UNKNOWN_MESSAGE , ///< Unknown type of input message - bad_syntax = RLM_SYNC_ERR_CLIENT_BAD_SYNTAX , ///< Bad syntax in input message head - limits_exceeded = RLM_SYNC_ERR_CLIENT_LIMITS_EXCEEDED , ///< Limits exceeded in input message - bad_session_ident = RLM_SYNC_ERR_CLIENT_BAD_SESSION_IDENT , ///< Bad session identifier in input message - bad_message_order = RLM_SYNC_ERR_CLIENT_BAD_MESSAGE_ORDER , ///< Bad input message order - bad_client_file_ident = RLM_SYNC_ERR_CLIENT_BAD_CLIENT_FILE_IDENT , ///< Bad client file identifier (IDENT) - bad_progress = RLM_SYNC_ERR_CLIENT_BAD_PROGRESS , ///< Bad progress information (DOWNLOAD) - bad_changeset_header_syntax = RLM_SYNC_ERR_CLIENT_BAD_CHANGESET_HEADER_SYNTAX, ///< Bad syntax in changeset header (DOWNLOAD) - bad_changeset_size = RLM_SYNC_ERR_CLIENT_BAD_CHANGESET_SIZE , ///< Bad changeset size in changeset header (DOWNLOAD) - bad_origin_file_ident = RLM_SYNC_ERR_CLIENT_BAD_ORIGIN_FILE_IDENT , ///< Bad origin file identifier in changeset header (DOWNLOAD) - bad_server_version = RLM_SYNC_ERR_CLIENT_BAD_SERVER_VERSION , ///< Bad server version in changeset header (DOWNLOAD) - bad_changeset = RLM_SYNC_ERR_CLIENT_BAD_CHANGESET , ///< Bad changeset (DOWNLOAD) - bad_request_ident = RLM_SYNC_ERR_CLIENT_BAD_REQUEST_IDENT , ///< Bad request identifier (MARK) - bad_error_code = RLM_SYNC_ERR_CLIENT_BAD_ERROR_CODE , ///< Bad error code (ERROR), - bad_compression = RLM_SYNC_ERR_CLIENT_BAD_COMPRESSION , ///< Bad compression (DOWNLOAD) - bad_client_version = RLM_SYNC_ERR_CLIENT_BAD_CLIENT_VERSION , ///< Bad last integrated client version in changeset header (DOWNLOAD) - ssl_server_cert_rejected = RLM_SYNC_ERR_CLIENT_SSL_SERVER_CERT_REJECTED , ///< SSL server certificate rejected - pong_timeout = RLM_SYNC_ERR_CLIENT_PONG_TIMEOUT , ///< Timeout on reception of PONG respone message - bad_client_file_ident_salt = RLM_SYNC_ERR_CLIENT_BAD_CLIENT_FILE_IDENT_SALT , ///< Bad client file identifier salt (IDENT) - bad_file_ident = RLM_SYNC_ERR_CLIENT_BAD_FILE_IDENT , ///< Bad file identifier (ALLOC) - connect_timeout = RLM_SYNC_ERR_CLIENT_CONNECT_TIMEOUT , ///< Sync connection was not fully established in time - bad_timestamp = RLM_SYNC_ERR_CLIENT_BAD_TIMESTAMP , ///< Bad timestamp (PONG) - bad_protocol_from_server = RLM_SYNC_ERR_CLIENT_BAD_PROTOCOL_FROM_SERVER , ///< Bad or missing protocol version information from server - client_too_old_for_server = RLM_SYNC_ERR_CLIENT_CLIENT_TOO_OLD_FOR_SERVER , ///< Protocol version negotiation failed: Client is too old for server - client_too_new_for_server = RLM_SYNC_ERR_CLIENT_CLIENT_TOO_NEW_FOR_SERVER , ///< Protocol version negotiation failed: Client is too new for server - protocol_mismatch = RLM_SYNC_ERR_CLIENT_PROTOCOL_MISMATCH , ///< Protocol version negotiation failed: No version supported by both client and server - bad_state_message = RLM_SYNC_ERR_CLIENT_BAD_STATE_MESSAGE , ///< Bad values in state message (STATE) - missing_protocol_feature = RLM_SYNC_ERR_CLIENT_MISSING_PROTOCOL_FEATURE , ///< Requested feature missing in negotiated protocol version - http_tunnel_failed = RLM_SYNC_ERR_CLIENT_HTTP_TUNNEL_FAILED , ///< Failed to establish HTTP tunnel with configured proxy - auto_client_reset_failure = RLM_SYNC_ERR_CLIENT_AUTO_CLIENT_RESET_FAILURE , ///< A fatal error was encountered which prevents completion of a client reset - // clang-format on -}; - -const std::error_category& client_error_category() noexcept; - -std::error_code make_error_code(ClientError) noexcept; - -ProtocolError client_error_to_protocol_error(ClientError); } // namespace realm::sync -namespace std { - -template <> -struct is_error_code_enum { - static const bool value = true; -}; - -} // namespace std - namespace realm::sync { static constexpr milliseconds_type default_connect_timeout = 120000; // 2 minutes diff --git a/src/realm/sync/config.hpp b/src/realm/sync/config.hpp index cd786150419..ae3ab7409f5 100644 --- a/src/realm/sync/config.hpp +++ b/src/realm/sync/config.hpp @@ -123,7 +123,6 @@ enum class SyncClientHookEvent { BootstrapMessageProcessed, BootstrapProcessed, ErrorMessageReceived, - BinaryMessageReceived, }; enum class SyncClientHookAction { @@ -131,7 +130,6 @@ enum class SyncClientHookAction { EarlyReturn, SuspendWithRetryableError, TriggerReconnect, - SimulateReadError, // Valid with BinaryMessageReceived }; inline std::ostream& operator<<(std::ostream& os, SyncClientHookAction action) diff --git a/src/realm/sync/network/websocket.cpp b/src/realm/sync/network/websocket.cpp index 9baea8c4748..2e815c90bd3 100644 --- a/src/realm/sync/network/websocket.cpp +++ b/src/realm/sync/network/websocket.cpp @@ -1141,85 +1141,82 @@ class HttpErrorCategory : public std::error_category { } }; -std::string error_string(WebSocketError code) +} // unnamed namespace + +namespace realm::sync::websocket { + +std::ostream& operator<<(std::ostream& os, WebSocketError code) { /// WebSocket error codes - switch (code) { - case WebSocketError::websocket_ok: - return "WebSocket: OK"; - case WebSocketError::websocket_going_away: - return "WebSocket: Going Away"; - case WebSocketError::websocket_protocol_error: - return "WebSocket: Protocol Error"; - case WebSocketError::websocket_unsupported_data: - return "WebSocket: Unsupported Data"; - case WebSocketError::websocket_reserved: - return "WebSocket: Reserved"; - case WebSocketError::websocket_no_status_received: - return "WebSocket: No Status Received"; - case WebSocketError::websocket_abnormal_closure: - return "WebSocket: Abnormal Closure"; - case WebSocketError::websocket_invalid_payload_data: - return "WebSocket: Invalid Payload Data"; - case WebSocketError::websocket_policy_violation: - return "WebSocket: Policy Violation"; - case WebSocketError::websocket_message_too_big: - return "WebSocket: Message Too Big"; - case WebSocketError::websocket_invalid_extension: - return "WebSocket: Invalid Extension"; - case WebSocketError::websocket_internal_server_error: - return "WebSocket: Internal Server Error"; - case WebSocketError::websocket_tls_handshake_failed: - return "WebSocket: TLS Handshake Failed"; - - /// WebSocket Errors - reported by server - case WebSocketError::websocket_unauthorized: - return "WebSocket: Unauthorized"; - case WebSocketError::websocket_forbidden: - return "WebSocket: Forbidden"; - case WebSocketError::websocket_moved_permanently: - return "WebSocket: Moved Permanently"; - case WebSocketError::websocket_client_too_old: - return "WebSocket: Client Too Old"; - case WebSocketError::websocket_client_too_new: - return "WebSocket: Client Too New"; - case WebSocketError::websocket_protocol_mismatch: - return "WebSocket: Protocol Mismatch"; - - case WebSocketError::websocket_resolve_failed: - return "WebSocket: Resolve Failed"; - case WebSocketError::websocket_connection_failed: - return "WebSocket: Connection Failed"; - case WebSocketError::websocket_read_error: - return "WebSocket: Read Error"; - case WebSocketError::websocket_write_error: - return "WebSocket: Write Error"; - case WebSocketError::websocket_retry_error: - return "WebSocket: Retry Error"; - case WebSocketError::websocket_fatal_error: - return "WebSocket: Fatal Error"; - } - return ""; -} + auto str = [&]() -> const char* { + switch (code) { + case WebSocketError::websocket_ok: + return "WebSocket: OK"; + case WebSocketError::websocket_going_away: + return "WebSocket: Going Away"; + case WebSocketError::websocket_protocol_error: + return "WebSocket: Protocol Error"; + case WebSocketError::websocket_unsupported_data: + return "WebSocket: Unsupported Data"; + case WebSocketError::websocket_reserved: + return "WebSocket: Reserved"; + case WebSocketError::websocket_no_status_received: + return "WebSocket: No Status Received"; + case WebSocketError::websocket_abnormal_closure: + return "WebSocket: Abnormal Closure"; + case WebSocketError::websocket_invalid_payload_data: + return "WebSocket: Invalid Payload Data"; + case WebSocketError::websocket_policy_violation: + return "WebSocket: Policy Violation"; + case WebSocketError::websocket_message_too_big: + return "WebSocket: Message Too Big"; + case WebSocketError::websocket_invalid_extension: + return "WebSocket: Invalid Extension"; + case WebSocketError::websocket_internal_server_error: + return "WebSocket: Internal Server Error"; + case WebSocketError::websocket_tls_handshake_failed: + return "WebSocket: TLS Handshake Failed"; -class WebSocketErrorCategory : public std::error_category { - const char* name() const noexcept final - { - return "realm::sync::websocket::WebSocketError"; + /// WebSocket Errors - reported by server + case WebSocketError::websocket_unauthorized: + return "WebSocket: Unauthorized"; + case WebSocketError::websocket_forbidden: + return "WebSocket: Forbidden"; + case WebSocketError::websocket_moved_permanently: + return "WebSocket: Moved Permanently"; + case WebSocketError::websocket_client_too_old: + return "WebSocket: Client Too Old"; + case WebSocketError::websocket_client_too_new: + return "WebSocket: Client Too New"; + case WebSocketError::websocket_protocol_mismatch: + return "WebSocket: Protocol Mismatch"; + + case WebSocketError::websocket_resolve_failed: + return "WebSocket: Resolve Failed"; + case WebSocketError::websocket_connection_failed: + return "WebSocket: Connection Failed"; + case WebSocketError::websocket_read_error: + return "WebSocket: Read Error"; + case WebSocketError::websocket_write_error: + return "WebSocket: Write Error"; + case WebSocketError::websocket_retry_error: + return "WebSocket: Retry Error"; + case WebSocketError::websocket_fatal_error: + return "WebSocket: Fatal Error"; + } + return nullptr; + }(); + + if (str == nullptr) { + os << "WebSocket: Unkhown Error (" << static_cast>(code) << ")"; } - std::string message(int error_code) const final - { - // Converts an error_code to one of the pre-defined status codes in - // https://tools.ietf.org/html/rfc6455#section-7.4.1 - auto msg = error_string(static_cast(error_code)); - if (msg.empty()) - msg = "Unknown error"; - return msg; + else { + os << str; } -}; - -} // unnamed namespace + return os; +} +} // namespace realm::sync::websocket bool websocket::Config::websocket_text_message_received(const char*, size_t) { @@ -1339,17 +1336,6 @@ util::Optional websocket::make_http_response(const HTTPRequest& re return do_make_http_response(request, sec_websocket_protocol, ec); } -const std::error_category& websocket::websocket_error_category() noexcept -{ - static const WebSocketErrorCategory category = {}; - return category; -} - -std::error_code websocket::make_error_code(WebSocketError error) noexcept -{ - return std::error_code{int(error), websocket_error_category()}; -} - const std::error_category& websocket::http_error_category() noexcept { static const HttpErrorCategory category = {}; @@ -1360,25 +1346,3 @@ std::error_code websocket::make_error_code(HttpError error_code) noexcept { return std::error_code{int(error_code), http_error_category()}; } - -ErrorCodes::Error websocket::get_simplified_websocket_error(WebSocketError error) -{ - if (error == sync::websocket::WebSocketError::websocket_resolve_failed) { - return ErrorCodes::WebSocketResolveFailedError; - } - else if (error == sync::websocket::WebSocketError::websocket_connection_failed || - error == sync::websocket::WebSocketError::websocket_unauthorized || - error == sync::websocket::WebSocketError::websocket_forbidden || - error == sync::websocket::WebSocketError::websocket_moved_permanently || - error == sync::websocket::WebSocketError::websocket_client_too_old || - error == sync::websocket::WebSocketError::websocket_client_too_new || - error == sync::websocket::WebSocketError::websocket_protocol_mismatch || - error == sync::websocket::WebSocketError::websocket_read_error || - error == sync::websocket::WebSocketError::websocket_write_error || - error == sync::websocket::WebSocketError::websocket_retry_error || - error == sync::websocket::WebSocketError::websocket_fatal_error) { - return ErrorCodes::WebSocketConnectionClosedClientError; - } - - return ErrorCodes::WebSocketConnectionClosedServerError; -} diff --git a/src/realm/sync/network/websocket.hpp b/src/realm/sync/network/websocket.hpp index 25a422ca9e9..0c5e21e7788 100644 --- a/src/realm/sync/network/websocket.hpp +++ b/src/realm/sync/network/websocket.hpp @@ -222,12 +222,6 @@ const std::error_category& http_error_category() noexcept; std::error_code make_error_code(HttpError) noexcept; -const std::error_category& websocket_error_category() noexcept; - -std::error_code make_error_code(WebSocketError) noexcept; - -ErrorCodes::Error get_simplified_websocket_error(WebSocketError); - } // namespace realm::sync::websocket namespace std { @@ -237,9 +231,4 @@ struct is_error_code_enum { static const bool value = true; }; -template <> -struct is_error_code_enum { - static const bool value = true; -}; - } // namespace std diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp index cbbbf711545..7d9bb1a03bb 100644 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ b/src/realm/sync/noinst/client_impl_base.cpp @@ -486,10 +486,12 @@ bool Connection::websocket_binary_message_received(util::Span data) return false; } - if ( using sf = SimulatedFailure; if (sf::check_trigger(sf::sync_client__read_head)) { - read_or_write_error(sf::sync_server__read_head, "simulated read error"); + close_due_to_client_side_error( + sf::make_simulated_failure_status(SimulatedFailure::FailureType::sync_client__read_head, + ErrorCodes::ConnectionClosed), + IsFatal{false}, ConnectionTerminationReason::read_or_write_error); // Throws return bool(m_websocket); } @@ -1088,13 +1090,6 @@ void Connection::handle_disconnect_wait(Status status) } -void Connection::read_or_write_error(std::error_code, std::string_view msg) -{ - close_due_to_client_side_error({ErrorCodes::ConnectionClosed, msg}, IsFatal{false}, - ConnectionTerminationReason::read_or_write_error); // Throws -} - - void Connection::close_due_to_protocol_error(Status status) { close_due_to_client_side_error(std::move(status), IsFatal{true}, @@ -1506,7 +1501,7 @@ void Session::on_integration_failure(const IntegrationException& error) { REALM_ASSERT_EX(m_state == Active, m_state); REALM_ASSERT(!m_client_error && !m_error_to_send); - logger.error("Failed to integrate downloaded changesets: %1", error.what()); + logger.error("Failed to integrate downloaded changesets: %1", error.to_status()); m_client_error = util::make_optional(error); m_error_to_send = true; diff --git a/src/realm/sync/noinst/client_impl_base.hpp b/src/realm/sync/noinst/client_impl_base.hpp index fde629657bc..f1d5a6e945e 100644 --- a/src/realm/sync/noinst/client_impl_base.hpp +++ b/src/realm/sync/noinst/client_impl_base.hpp @@ -564,7 +564,6 @@ class ClientImpl::Connection { void handle_message_received(util::Span data); void initiate_disconnect_wait(); void handle_disconnect_wait(Status status); - void read_or_write_error(std::error_code ec, std::string_view msg); void close_due_to_protocol_error(Status status); void close_due_to_network_error(Status, IsFatal, ConnectionTerminationReason, websocket::WebSocketError error); diff --git a/test/object-store/c_api/c_api.cpp b/test/object-store/c_api/c_api.cpp index 8f03f2a655d..6299be4d5bf 100644 --- a/test/object-store/c_api/c_api.cpp +++ b/test/object-store/c_api/c_api.cpp @@ -679,17 +679,9 @@ TEST_CASE("C API (non-database)", "[c_api]") { using namespace realm::sync; std::string message; - std::error_code error_code = make_error_code(sync::ClientError::connection_closed); - realm_sync_error_code_t error = c_api::to_capi(SystemError(error_code, "").to_status(), message); - CHECK(error.category == realm_sync_error_category_e::RLM_SYNC_ERROR_CATEGORY_CLIENT); - CHECK(error.value == int(error_code.value())); - CHECK(error_code.message() == error.message); - CHECK(message == error.message); - + std::error_code error_code; + realm_sync_error_code_t error; std::error_code ec_check; - c_api::sync_error_to_error_code(error, &ec_check); - CHECK(ec_check.category() == realm::sync::client_error_category()); - CHECK(ec_check.value() == int(error_code.value())); error_code = make_error_code(sync::ProtocolError::connection_closed); error = c_api::to_capi(SystemError(error_code, "").to_status(), message); @@ -715,16 +707,6 @@ TEST_CASE("C API (non-database)", "[c_api]") { CHECK(ec_check.category() == std::system_category()); CHECK(ec_check.value() == int(error_code.value())); - error_code.assign(ErrorCodes::WebSocketResolveFailedError, - realm::sync::websocket::websocket_error_category()); - error = c_api::to_capi(SystemError(error_code, "").to_status(), message); - CHECK(error.category == realm_sync_error_category_e::RLM_SYNC_ERROR_CATEGORY_WEBSOCKET); - CHECK(error.value == realm_errno::RLM_ERR_WEBSOCKET_RESOLVE_FAILED_ERROR); - - c_api::sync_error_to_error_code(error, &ec_check); - CHECK(ec_check.category() == realm::sync::websocket::websocket_error_category()); - CHECK(ec_check.value() == int(error_code.value())); - error_code = make_error_code(util::error::misc_errors::unknown); error = c_api::to_capi(SystemError(error_code, "").to_status(), message); CHECK(error.category == realm_sync_error_category_e::RLM_SYNC_ERROR_CATEGORY_UNKNOWN); diff --git a/test/test_sync.cpp b/test/test_sync.cpp index 6dfa5fba48f..26f2fa5b491 100644 --- a/test/test_sync.cpp +++ b/test/test_sync.cpp @@ -792,9 +792,8 @@ struct ExpectChangesetError { REALM_ASSERT(error_info); CHECK_EQUAL(error_info->status, ErrorCodes::BadChangeset); CHECK(!error_info->is_fatal()); - CHECK_EQUAL(error_info->message, - "Bad changeset (DOWNLOAD): Failed to transform received changeset: Schema mismatch: " + - expected_error); + CHECK_EQUAL(error_info->status.reason(), + "Failed to transform received changeset: Schema mismatch: " + expected_error); fixture.stop(); } }; @@ -1468,7 +1467,41 @@ TEST(Sync_Randomized) } } +#ifdef REALM_DEBUG // Failure simulation only works in debug mode +TEST(Sync_ReadFailureSimulation) +{ + TEST_DIR(server_dir); + TEST_CLIENT_DB(db); + + // Check that read failure simulation works on the client-side + { + bool client_side_read_did_fail = false; + { + ClientServerFixture fixture(server_dir, test_context); + fixture.set_client_side_error_rate(1, 1); // 100% chance of failure + auto error_handler = [&](Status status, bool is_fatal) { + using sf = _impl::SimulatedFailure; + CHECK(status.has_extra_info()); + CHECK_EQUAL(status.get_extra_info().failure_type, + sf::FailureType::sync_client__read_head); + CHECK_NOT(is_fatal); + client_side_read_did_fail = true; + fixture.stop(); + }; + fixture.set_client_side_error_handler(error_handler); + Session session = fixture.make_bound_session(db, "/test"); + fixture.start(); + session.wait_for_download_complete_or_client_stopped(); + } + CHECK(client_side_read_did_fail); + } + + // FIXME: Figure out a way to check that read failure simulation works on + // the server-side +} + +#endif // REALM_DEBUG TEST(Sync_FailingReadsOnClientSide) { TEST_CLIENT_DB(db_1); @@ -1479,8 +1512,13 @@ TEST(Sync_FailingReadsOnClientSide) ClientServerFixture fixture{dir, test_context}; fixture.set_client_side_error_rate(5, 100); // 5% chance of failure auto error_handler = [&](Status status, bool) { - if (CHECK_EQUAL(status, ErrorCodes::ConnectionClosed)) + using sf = _impl::SimulatedFailure; + if (CHECK(status.has_extra_info())) { + CHECK_EQUAL(status.get_extra_info().failure_type, + sf::FailureType::sync_client__read_head); + CHECK_EQUAL(status, ErrorCodes::ConnectionClosed); fixture.cancel_reconnect_delay(); + } }; fixture.set_client_side_error_handler(error_handler); fixture.start(); @@ -1538,7 +1576,7 @@ TEST(Sync_FailingReadsOnServerSide) TEST_DIR(dir); ClientServerFixture fixture{dir, test_context}; fixture.set_server_side_error_rate(5, 100); // 5% chance of failure - auto error_handler = [&](std::error_code, bool is_fatal, const std::string&) { + auto error_handler = [&](Status, bool is_fatal) { CHECK_NOT(is_fatal); fixture.cancel_reconnect_delay(); }; From 91a29b80a2dd301d3c8c4b646013ab68213c4c77 Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Wed, 12 Jul 2023 18:32:35 -0400 Subject: [PATCH 03/30] add websocket_error.hpp header --- src/realm/sync/network/websocket_error.hpp | 41 ++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/realm/sync/network/websocket_error.hpp diff --git a/src/realm/sync/network/websocket_error.hpp b/src/realm/sync/network/websocket_error.hpp new file mode 100644 index 00000000000..f66afea0912 --- /dev/null +++ b/src/realm/sync/network/websocket_error.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include + +#include + +namespace realm::sync::websocket { +enum class WebSocketError { + websocket_ok = RLM_ERR_WEBSOCKET_OK, + websocket_going_away = RLM_ERR_WEBSOCKET_GOINGAWAY, + websocket_protocol_error = RLM_ERR_WEBSOCKET_PROTOCOLERROR, + websocket_unsupported_data = RLM_ERR_WEBSOCKET_UNSUPPORTEDDATA, + websocket_reserved = RLM_ERR_WEBSOCKET_RESERVED, + websocket_no_status_received = RLM_ERR_WEBSOCKET_NOSTATUSRECEIVED, + websocket_abnormal_closure = RLM_ERR_WEBSOCKET_ABNORMALCLOSURE, + websocket_invalid_payload_data = RLM_ERR_WEBSOCKET_INVALIDPAYLOADDATA, + websocket_policy_violation = RLM_ERR_WEBSOCKET_POLICYVIOLATION, + websocket_message_too_big = RLM_ERR_WEBSOCKET_MESSAGETOOBIG, + websocket_invalid_extension = RLM_ERR_WEBSOCKET_INAVALIDEXTENSION, + websocket_internal_server_error = RLM_ERR_WEBSOCKET_INTERNALSERVERERROR, + websocket_tls_handshake_failed = RLM_ERR_WEBSOCKET_TLSHANDSHAKEFAILED, // Used by default WebSocket + + // WebSocket Errors - reported by server + websocket_unauthorized = RLM_ERR_WEBSOCKET_UNAUTHORIZED, + websocket_forbidden = RLM_ERR_WEBSOCKET_FORBIDDEN, + websocket_moved_permanently = RLM_ERR_WEBSOCKET_MOVEDPERMANENTLY, + websocket_client_too_old = RLM_ERR_WEBSOCKET_CLIENT_TOO_OLD, + websocket_client_too_new = RLM_ERR_WEBSOCKET_CLIENT_TOO_NEW, + websocket_protocol_mismatch = RLM_ERR_WEBSOCKET_PROTOCOL_MISMATCH, + + websocket_resolve_failed = RLM_ERR_WEBSOCKET_RESOLVE_FAILED, + websocket_connection_failed = RLM_ERR_WEBSOCKET_CONNECTION_FAILED, + websocket_read_error = RLM_ERR_WEBSOCKET_READ_ERROR, + websocket_write_error = RLM_ERR_WEBSOCKET_WRITE_ERROR, + websocket_retry_error = RLM_ERR_WEBSOCKET_RETRY_ERROR, + websocket_fatal_error = RLM_ERR_WEBSOCKET_FATAL_ERROR, +}; + +std::ostream& operator<<(std::ostream&, WebSocketError); + +} // namespace realm::sync::websocket From 04a83d29a1f930b498817302ea9591e1945bf967 Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Thu, 13 Jul 2023 12:10:12 -0400 Subject: [PATCH 04/30] remove C API realm_sync_error_code and remaining public uses of std::error_code --- src/realm.h | 40 ++----------- src/realm/object-store/audit.mm | 4 +- src/realm/object-store/c_api/conversion.hpp | 10 +++- .../object-store/c_api/socket_provider.cpp | 11 ++-- src/realm/object-store/c_api/sync.cpp | 60 +++---------------- src/realm/object-store/sync/sync_session.cpp | 21 ++++--- src/realm/status.hpp | 23 ------- src/realm/sync/client.cpp | 26 ++++---- src/realm/sync/client.hpp | 8 +-- test/object-store/c_api/c_api.cpp | 56 ++--------------- test/object-store/sync/app.cpp | 4 +- test/test_sync.cpp | 36 +++++------ 12 files changed, 74 insertions(+), 225 deletions(-) diff --git a/src/realm.h b/src/realm.h index 9db6734b178..01988877fb0 100644 --- a/src/realm.h +++ b/src/realm.h @@ -3334,26 +3334,6 @@ typedef enum realm_sync_progress_direction { RLM_SYNC_PROGRESS_DIRECTION_DOWNLOAD, } realm_sync_progress_direction_e; -/** - * Possible error categories realm_sync_error_code_t can fall in. - */ -typedef enum realm_sync_error_category { - RLM_SYNC_ERROR_CATEGORY_CLIENT, - RLM_SYNC_ERROR_CATEGORY_CONNECTION, - RLM_SYNC_ERROR_CATEGORY_SESSION, - RLM_SYNC_ERROR_CATEGORY_WEBSOCKET, - - /** - * System error - POSIX errno, Win32 HRESULT, etc. - */ - RLM_SYNC_ERROR_CATEGORY_SYSTEM, - - /** - * Unknown source of error. - */ - RLM_SYNC_ERROR_CATEGORY_UNKNOWN, -} realm_sync_error_category_e; - typedef enum realm_sync_error_action { RLM_SYNC_ERROR_ACTION_NO_ACTION, RLM_SYNC_ERROR_ACTION_PROTOCOL_VIOLATION, @@ -3370,17 +3350,6 @@ typedef enum realm_sync_error_action { typedef struct realm_sync_session realm_sync_session_t; typedef struct realm_async_open_task realm_async_open_task_t; -// This type should never be returned from a function. -// It's only meant as an asynchronous callback argument. -// Pointers to this struct and its pointer members are only valid inside the scope -// of the callback they were passed to. -typedef struct realm_sync_error_code { - realm_sync_error_category_e category; - int value; - const char* message; - const char* category_name; -} realm_sync_error_code_t; - typedef struct realm_sync_error_user_info { const char* key; const char* value; @@ -3397,8 +3366,7 @@ typedef struct realm_sync_error_compensating_write_info { // Pointers to this struct and its pointer members are only valid inside the scope // of the callback they were passed to. typedef struct realm_sync_error { - realm_sync_error_code_t error_code; - const char* detailed_message; + realm_error_t status; const char* c_original_file_path_key; const char* c_recovery_file_path_key; bool is_fatal; @@ -3422,7 +3390,7 @@ typedef struct realm_sync_error { * * @param error Null, if the operation completed successfully. */ -typedef void (*realm_sync_wait_for_completion_func_t)(realm_userdata_t userdata, realm_sync_error_code_t* error); +typedef void (*realm_sync_wait_for_completion_func_t)(realm_userdata_t userdata, realm_error_t* error); typedef void (*realm_sync_connection_state_changed_func_t)(realm_userdata_t userdata, realm_sync_connection_state_e old_state, realm_sync_connection_state_e new_state); @@ -4106,8 +4074,8 @@ RLM_API realm_sync_socket_t* realm_sync_socket_new( realm_sync_socket_websocket_async_write_func_t websocket_write_func, realm_sync_socket_websocket_free_func_t websocket_free_func); -RLM_API void realm_sync_socket_callback_complete(realm_sync_socket_callback_t* realm_callback, - realm_web_socket_errno_e status, const char* reason); +RLM_API void realm_sync_socket_callback_complete(realm_sync_socket_callback_t* realm_callback, realm_errno_e status, + const char* reason); RLM_API void realm_sync_socket_websocket_connected(realm_websocket_observer_t* realm_websocket_observer, const char* protocol); diff --git a/src/realm/object-store/audit.mm b/src/realm/object-store/audit.mm index 4f23366a2d8..3b454075f20 100644 --- a/src/realm/object-store/audit.mm +++ b/src/realm/object-store/audit.mm @@ -783,8 +783,8 @@ bool write_event(Timestamp timestamp, StringData activity, StringData event_type std::string path = session->path(); session->close(); m_open_paths.erase(path); - if (status.get_std_error_code()) { - m_logger->error("Events: Upload on '%1' failed with error '%2'.", path, status.reason()); + if (!status.is_ok()) { + m_logger->error("Events: Upload on '%1' failed with error '%2'.", path, status); if (m_error_handler) { m_error_handler(SyncError(status, false)); } diff --git a/src/realm/object-store/c_api/conversion.hpp b/src/realm/object-store/c_api/conversion.hpp index 28147d1383b..c3812c6be7c 100644 --- a/src/realm/object-store/c_api/conversion.hpp +++ b/src/realm/object-store/c_api/conversion.hpp @@ -485,8 +485,14 @@ static inline realm_version_id_t to_capi(const VersionID& v) return version_id; } -realm_sync_error_code_t to_capi(const Status& status, std::string& message); -void sync_error_to_error_code(const realm_sync_error_code_t& sync_error_code, std::error_code* error_code_out); +static inline realm_error_t to_capi(const Status s) +{ + realm_error_t err; + err.error = static_cast(s.code()); + err.categories = static_cast(ErrorCodes::error_categories(s.code()).value()); + err.message = s.reason().c_str(); + return err; +} } // namespace realm::c_api diff --git a/src/realm/object-store/c_api/socket_provider.cpp b/src/realm/object-store/c_api/socket_provider.cpp index 5a6bdc4f171..47c005005b5 100644 --- a/src/realm/object-store/c_api/socket_provider.cpp +++ b/src/realm/object-store/c_api/socket_provider.cpp @@ -218,14 +218,11 @@ RLM_API realm_sync_socket_t* realm_sync_socket_new( }); } -RLM_API void realm_sync_socket_callback_complete(realm_sync_socket_callback* realm_callback, - realm_web_socket_errno_e code, const char* reason) +RLM_API void realm_sync_socket_callback_complete(realm_sync_socket_callback* realm_callback, realm_errno_e code, + const char* reason) { - auto close_code = sync::websocket::WebSocketError(code); - - auto complete_status = code == realm_web_socket_errno_e::RLM_ERR_WEBSOCKET_OK - ? Status::OK() - : Status{ErrorCodes::ConnectionClosed, reason}; + auto complete_status = + code == realm_errno_e::RLM_ERR_NONE ? Status::OK() : Status{static_cast(code), reason}; (*(realm_callback->get()))(complete_status); realm_release(realm_callback); } diff --git a/src/realm/object-store/c_api/sync.cpp b/src/realm/object-store/c_api/sync.cpp index 86fea7c0c21..fb5eddb0ab2 100644 --- a/src/realm/object-store/c_api/sync.cpp +++ b/src/realm/object-store/c_api/sync.cpp @@ -122,52 +122,6 @@ static_assert(realm_flx_sync_subscription_set_state_e(SubscriptionSet::State::Un } // namespace -realm_sync_error_code_t to_capi(const Status& status, std::string& message) -{ - auto ret = realm_sync_error_code_t(); - - auto error_code = status.get_std_error_code(); - const std::error_category& category = error_code.category(); - if (category == realm::sync::protocol_error_category()) { - if (realm::sync::is_session_level_error(realm::sync::ProtocolError(error_code.value()))) { - ret.category = RLM_SYNC_ERROR_CATEGORY_SESSION; - } - else { - ret.category = RLM_SYNC_ERROR_CATEGORY_CONNECTION; - } - } - else if (category == std::system_category() || category == realm::util::error::basic_system_error_category()) { - ret.category = RLM_SYNC_ERROR_CATEGORY_SYSTEM; - } - else { - ret.category = RLM_SYNC_ERROR_CATEGORY_UNKNOWN; - } - - ret.value = error_code.value(); - message = error_code.message(); // pass the string to the caller for lifetime purposes - ret.message = message.c_str(); - ret.category_name = category.name(); - - - return ret; -} - -void sync_error_to_error_code(const realm_sync_error_code_t& sync_error_code, std::error_code* error_code_out) -{ - if (error_code_out) { - const realm_sync_error_category_e category = sync_error_code.category; - if (category == RLM_SYNC_ERROR_CATEGORY_SESSION || category == RLM_SYNC_ERROR_CATEGORY_CONNECTION) { - error_code_out->assign(sync_error_code.value, realm::sync::protocol_error_category()); - } - else if (category == RLM_SYNC_ERROR_CATEGORY_SYSTEM) { - error_code_out->assign(sync_error_code.value, std::system_category()); - } - else if (category == RLM_SYNC_ERROR_CATEGORY_UNKNOWN) { - error_code_out->assign(sync_error_code.value, realm::util::error::basic_system_error_category()); - } - } -} - static Query add_ordering_to_realm_query(Query realm_query, const DescriptorOrdering& ordering) { auto ordering_copy = util::make_bind(); @@ -302,8 +256,10 @@ RLM_API void realm_sync_config_set_error_handler(realm_sync_config_t* config, re auto c_error = realm_sync_error_t(); std::string error_code_message; - c_error.error_code = to_capi(error.to_status(), error_code_message); - c_error.detailed_message = error.what(); + auto status = error.to_status(); + c_error.status.message = status.reason().c_str(); + c_error.status.error = static_cast(status.code()); + c_error.status.categories = ErrorCodes::error_categories(status.code()).value(); c_error.is_fatal = error.is_fatal; c_error.is_unrecognized_by_client = error.is_unrecognized_by_client; c_error.is_client_reset_requested = error.is_client_reset_requested(); @@ -843,9 +799,9 @@ RLM_API void realm_sync_session_wait_for_download_completion(realm_sync_session_ { util::UniqueFunction cb = [done, userdata = SharedUserdata(userdata, FreeUserdata(userdata_free))](Status s) { - if (s.get_std_error_code()) { + if (!s.is_ok()) { std::string error_code_message; - realm_sync_error_code_t error = to_capi(s, error_code_message); + realm_error_t error = to_capi(s); done(userdata.get(), &error); } else { @@ -862,9 +818,9 @@ RLM_API void realm_sync_session_wait_for_upload_completion(realm_sync_session_t* { util::UniqueFunction cb = [done, userdata = SharedUserdata(userdata, FreeUserdata(userdata_free))](Status s) { - if (s.get_std_error_code()) { + if (!s.is_ok()) { std::string error_code_message; - realm_sync_error_code_t error = to_capi(s, error_code_message); + realm_error_t error = to_capi(s); done(userdata.get(), &error); } else { diff --git a/src/realm/object-store/sync/sync_session.cpp b/src/realm/object-store/sync/sync_session.cpp index 64b4911b1e2..7d4e9212140 100644 --- a/src/realm/object-store/sync/sync_session.cpp +++ b/src/realm/object-store/sync/sync_session.cpp @@ -141,15 +141,14 @@ void SyncSession::become_dying(util::CheckedUniqueLock lock) } size_t current_death_count = ++m_death_count; - m_session->async_wait_for_upload_completion( - [weak_session = weak_from_this(), current_death_count](std::error_code) { - if (auto session = weak_session.lock()) { - util::CheckedUniqueLock lock(session->m_state_mutex); - if (session->m_state == State::Dying && session->m_death_count == current_death_count) { - session->become_inactive(std::move(lock)); - } + m_session->async_wait_for_upload_completion([weak_session = weak_from_this(), current_death_count](Status) { + if (auto session = weak_session.lock()) { + util::CheckedUniqueLock lock(session->m_state_mutex); + if (session->m_state == State::Dying && session->m_death_count == current_death_count) { + session->become_inactive(std::move(lock)); } - }); + } + }); m_state_mutex.unlock(lock); } @@ -224,7 +223,7 @@ void SyncSession::do_become_inactive(util::CheckedUniqueLock lock, Status status m_connection_change_notifier.invoke_callbacks(old_state, connection_state()); } - if (!status.get_std_error_code()) + if (status.is_ok()) status = Status(ErrorCodes::OperationAborted, "Sync session became inactive"); // Inform any queued-up completion handlers that they were cancelled. @@ -1201,7 +1200,7 @@ void SyncSession::add_completion_callback(util::UniqueFunction cal auto waiter = is_download ? &sync::Session::async_wait_for_download_completion : &sync::Session::async_wait_for_upload_completion; - (m_session.get()->*waiter)([weak_self = weak_from_this(), id = m_completion_request_counter](std::error_code ec) { + (m_session.get()->*waiter)([weak_self = weak_from_this(), id = m_completion_request_counter](Status status) { auto self = weak_self.lock(); if (!self) return; @@ -1209,7 +1208,7 @@ void SyncSession::add_completion_callback(util::UniqueFunction cal auto callback_node = self->m_completion_callbacks.extract(id); lock.unlock(); if (callback_node) { - callback_node.mapped().second(Status(ec, "")); + callback_node.mapped().second(status); } }); } diff --git a/src/realm/status.hpp b/src/realm/status.hpp index 46fcb5113a0..a3ae3df37b4 100644 --- a/src/realm/status.hpp +++ b/src/realm/status.hpp @@ -55,20 +55,6 @@ class REALM_NODISCARD Status { { } - template , int> = 0> - Status(std::error_code code, Reason&& reason) - { - if (code) { - m_error = ErrorInfo::create(ErrorCodes::SystemError, std::string{std::string_view{reason}}, nullptr); - m_error->m_std_error_code = code; - } - } - - std::error_code get_std_error_code() const - { - return m_error ? m_error->m_std_error_code : std::error_code{}; - } - /* * Copying a Status is just copying an intrusive pointer - i.e. very cheap. Moving them is similarly cheap. */ @@ -114,21 +100,12 @@ class REALM_NODISCARD Status { } private: - friend struct SystemError; - void set_std_error_code(std::error_code code) - { - m_error->m_std_error_code = code; - } - Status() = default; struct ErrorInfo { mutable std::atomic m_refs; const ErrorCodes::Error m_code; const std::string m_reason; - // The addition of this system error code may be temporary if we ever get all use of - // std::error_code migrated to the unified exception system - std::error_code m_std_error_code; std::unique_ptr m_extra_info; diff --git a/src/realm/sync/client.cpp b/src/realm/sync/client.cpp index 0ee02236e66..7893baf499d 100644 --- a/src/realm/sync/client.cpp +++ b/src/realm/sync/client.cpp @@ -1382,7 +1382,7 @@ void SessionWrapper::async_wait_for(bool upload_completion, bool download_comple REALM_ASSERT(self->m_actualized); if (REALM_UNLIKELY(!self->m_sess)) { // Already finalized - handler(util::error::operation_aborted); // Throws + handler({ErrorCodes::OperationAborted, "Session finalized before callback could run"}); // Throws return; } if (upload_completion) { @@ -1640,20 +1640,19 @@ void SessionWrapper::finalize() while (!m_upload_completion_handlers.empty()) { auto handler = std::move(m_upload_completion_handlers.back()); m_upload_completion_handlers.pop_back(); - std::error_code ec = error::operation_aborted; - handler(ec); // Throws + handler( + {ErrorCodes::OperationAborted, "Sync session is being finalized before upload was complete"}); // Throws } while (!m_download_completion_handlers.empty()) { auto handler = std::move(m_download_completion_handlers.back()); m_download_completion_handlers.pop_back(); - std::error_code ec = error::operation_aborted; - handler(ec); // Throws + handler( + {ErrorCodes::OperationAborted, "Sync session is being finalized before download was complete"}); // Throws } while (!m_sync_completion_handlers.empty()) { auto handler = std::move(m_sync_completion_handlers.back()); m_sync_completion_handlers.pop_back(); - std::error_code ec = error::operation_aborted; - handler(ec); // Throws + handler({ErrorCodes::OperationAborted, "Sync session is being finalized before sync was complete"}); // Throws } } @@ -1692,7 +1691,7 @@ void SessionWrapper::on_upload_completion() auto handler = std::move(m_upload_completion_handlers.back()); m_upload_completion_handlers.pop_back(); std::error_code ec; // Success - handler(ec); // Throws + handler(Status::OK()); // Throws } while (!m_sync_completion_handlers.empty()) { auto handler = std::move(m_sync_completion_handlers.back()); @@ -1712,8 +1711,7 @@ void SessionWrapper::on_download_completion() while (!m_download_completion_handlers.empty()) { auto handler = std::move(m_download_completion_handlers.back()); m_download_completion_handlers.pop_back(); - std::error_code ec; // Success - handler(ec); // Throws + handler(Status::OK()); // Throws } while (!m_sync_completion_handlers.empty()) { auto handler = std::move(m_sync_completion_handlers.back()); @@ -1829,13 +1827,13 @@ void SessionWrapper::handle_pending_client_reset_acknowledgement() m_sess->logger.info("Tracking pending client reset of type \"%1\" from %2", pending_reset->type, pending_reset->time); util::bind_ptr self(this); - async_wait_for(true, true, [self = std::move(self), pending_reset = *pending_reset](std::error_code ec) { - if (ec == util::error::operation_aborted) { + async_wait_for(true, true, [self = std::move(self), pending_reset = *pending_reset](Status status) { + if (status == ErrorCodes::OperationAborted) { return; } auto& logger = self->m_sess->logger; - if (ec) { - logger.error("Error while tracking client reset acknowledgement: %1", ec.message()); + if (!status.is_ok()) { + logger.error("Error while tracking client reset acknowledgement: %1", status); return; } diff --git a/src/realm/sync/client.hpp b/src/realm/sync/client.hpp index 0e246b93eda..71fe528399b 100644 --- a/src/realm/sync/client.hpp +++ b/src/realm/sync/client.hpp @@ -175,7 +175,7 @@ class Session { using ProgressHandler = void(std::uint_fast64_t downloaded_bytes, std::uint_fast64_t downloadable_bytes, std::uint_fast64_t uploaded_bytes, std::uint_fast64_t uploadable_bytes, std::uint_fast64_t progress_version, std::uint_fast64_t snapshot_version); - using WaitOperCompletionHandler = util::UniqueFunction; + using WaitOperCompletionHandler = util::UniqueFunction; using SSLVerifyCallback = bool(const std::string& server_address, port_type server_port, const char* pem_data, size_t pem_size, int preverify_ok, int depth); @@ -659,10 +659,10 @@ class Session { /// If incomplete wait operations exist when the session is terminated, /// those wait operations will be canceled. Session termination is an event /// that happens in the context of the client's event loop thread shortly - /// after the destruction of the session object. The std::error_code + /// after the destruction of the session object. The Status /// argument passed to the completion handler of a canceled wait operation - /// will be `util::error::operation_aborted`. For uncanceled wait operations - /// it will be `std::error_code{}`. Note that as long as the client's event + /// will be `ErrorCodes::OperationAborted`. For uncanceled wait operations + /// it will be `Status::OK()`. Note that as long as the client's event /// loop thread is running, all completion handlers will be called /// regardless of whether the operations get canceled or not. /// diff --git a/test/object-store/c_api/c_api.cpp b/test/object-store/c_api/c_api.cpp index 6299be4d5bf..1dcc9717005 100644 --- a/test/object-store/c_api/c_api.cpp +++ b/test/object-store/c_api/c_api.cpp @@ -675,48 +675,6 @@ TEST_CASE("C API (non-database)", "[c_api]") { }); } - SECTION("realm_sync_error_code") { - using namespace realm::sync; - std::string message; - - std::error_code error_code; - realm_sync_error_code_t error; - std::error_code ec_check; - - error_code = make_error_code(sync::ProtocolError::connection_closed); - error = c_api::to_capi(SystemError(error_code, "").to_status(), message); - CHECK(error.category == realm_sync_error_category_e::RLM_SYNC_ERROR_CATEGORY_CONNECTION); - - c_api::sync_error_to_error_code(error, &ec_check); - CHECK(ec_check.category() == realm::sync::protocol_error_category()); - CHECK(ec_check.value() == int(error_code.value())); - - error_code = make_error_code(sync::ProtocolError::session_closed); - error = c_api::to_capi(SystemError(error_code, "").to_status(), message); - CHECK(error.category == realm_sync_error_category_e::RLM_SYNC_ERROR_CATEGORY_SESSION); - - c_api::sync_error_to_error_code(error, &ec_check); - CHECK(ec_check.category() == realm::sync::protocol_error_category()); - CHECK(ec_check.value() == int(error_code.value())); - - error_code = make_error_code(realm::util::error::basic_system_errors::invalid_argument); - error = c_api::to_capi(SystemError(error_code, "").to_status(), message); - CHECK(error.category == realm_sync_error_category_e::RLM_SYNC_ERROR_CATEGORY_SYSTEM); - - c_api::sync_error_to_error_code(error, &ec_check); - CHECK(ec_check.category() == std::system_category()); - CHECK(ec_check.value() == int(error_code.value())); - - error_code = make_error_code(util::error::misc_errors::unknown); - error = c_api::to_capi(SystemError(error_code, "").to_status(), message); - CHECK(error.category == realm_sync_error_category_e::RLM_SYNC_ERROR_CATEGORY_UNKNOWN); - - c_api::sync_error_to_error_code(error, &ec_check); - CHECK(ec_check.category() == realm::util::error::basic_system_error_category()); - CHECK(ec_check.value() == int(error_code.value())); - } - - #endif // REALM_ENABLE_AUTH_TESTS } @@ -5051,7 +5009,6 @@ struct Userdata { realm_error_t error; realm_thread_safe_reference_t* realm_ref = nullptr; std::string error_message; - std::string error_catagory; }; #if REALM_ENABLE_SYNC @@ -5077,8 +5034,10 @@ static void sync_error_handler(void* p, realm_sync_session_t*, const realm_sync_ { auto userdata_p = static_cast(p); userdata_p->has_error = true; - userdata_p->error_message = error.error_code.message; - userdata_p->error_catagory = error.error_code.category_name; + userdata_p->error_message = error.status.message; + userdata_p->error.error = error.status.error; + userdata_p->error.categories = error.status.categories; + userdata_p->error.message = userdata_p->error_message.c_str(); } TEST_CASE("C API - async_open", "[c_api][sync]") { @@ -5159,7 +5118,6 @@ TEST_CASE("C API - async_open", "[c_api][sync]") { REQUIRE(userdata.called); REQUIRE(!userdata.realm_ref); REQUIRE(userdata.error_message == "Bad user authentication (BIND)"); - REQUIRE(userdata.error_catagory == "realm::sync::ProtocolError"); realm_release(task); realm_release(config); realm_release(sync_config); @@ -5702,9 +5660,7 @@ TEST_CASE("app: flx-sync compensating writes C API support", "[c_api][flx][sync] sync_config, [](realm_userdata_t user_data, realm_sync_session_t*, const realm_sync_error_t error) { auto state = reinterpret_cast(user_data); - REQUIRE(error.error_code.category == RLM_SYNC_ERROR_CATEGORY_SESSION); - REQUIRE(error.error_code.value == RLM_SYNC_ERR_SESSION_COMPENSATING_WRITE); - + REQUIRE(error.status.error == RLM_ERR_SYNC_COMPENSATING_WRITE); REQUIRE(error.compensating_writes_length > 0); std::lock_guard lk(state->mutex); @@ -6236,7 +6192,7 @@ TEST_CASE("C API app: websocket provider", "[c_api][sync][app]") { auto test_data = static_cast(userdata); REQUIRE(test_data); auto cb = [callback_copy = callback](Status s) { - realm_sync_socket_callback_complete(callback_copy, static_cast(s.code()), + realm_sync_socket_callback_complete(callback_copy, static_cast(s.code()), s.reason().c_str()); }; test_data->socket_provider->post(std::move(cb)); diff --git a/test/object-store/sync/app.cpp b/test/object-store/sync/app.cpp index 801dcf7f235..729d735f818 100644 --- a/test/object-store/sync/app.cpp +++ b/test/object-store/sync/app.cpp @@ -3057,9 +3057,9 @@ TEST_CASE("app: sync integration", "[sync][app][baas]") { CreatePolicy::ForceCreate); r->commit_transaction(); } - r->sync_session()->wait_for_upload_completion([&](Status ec) { + r->sync_session()->wait_for_upload_completion([&](Status status) { std::lock_guard lk(mutex); - REQUIRE(!ec.get_std_error_code()); + REQUIRE(status.is_ok()); done = true; }); r->sync_session()->resume(); diff --git a/test/test_sync.cpp b/test/test_sync.cpp index 26f2fa5b491..33a3c26816d 100644 --- a/test/test_sync.cpp +++ b/test/test_sync.cpp @@ -184,8 +184,8 @@ TEST(Sync_AsyncWaitForUploadCompletion) auto wait = [&] { BowlOfStonesSemaphore bowl; - auto handler = [&](std::error_code ec) { - if (CHECK_NOT(ec)) + auto handler = [&](Status s) { + if (CHECK(s.is_ok())) bowl.add_stone(); }; session.async_wait_for_upload_completion(handler); @@ -227,8 +227,8 @@ TEST(Sync_AsyncWaitForUploadCompletionNoPendingLocalChanges) auto pf = util::make_promise_future(); session.async_wait_for_upload_completion( - [promise = std::move(pf.promise), tr = db->start_read()](std::error_code ec) mutable { - REALM_ASSERT(!ec); + [promise = std::move(pf.promise), tr = db->start_read()](Status status) mutable { + REALM_ASSERT(status.is_ok()); tr->advance_read(); promise.emplace_value(tr->get_history()->no_pending_local_changes(tr->get_version())); }); @@ -246,8 +246,8 @@ TEST(Sync_AsyncWaitForDownloadCompletion) auto wait = [&](Session& session) { BowlOfStonesSemaphore bowl; - auto handler = [&](std::error_code ec) { - if (CHECK_NOT(ec)) + auto handler = [&](Status s) { + if (CHECK(s.is_ok())) bowl.add_stone(); }; session.async_wait_for_download_completion(handler); @@ -308,8 +308,8 @@ TEST(Sync_AsyncWaitForSyncCompletion) auto wait = [&](Session& session) { BowlOfStonesSemaphore bowl; - auto handler = [&](std::error_code ec) { - if (CHECK_NOT(ec)) + auto handler = [&](Status s) { + if (CHECK(s.is_ok())) bowl.add_stone(); }; session.async_wait_for_sync_completion(handler); @@ -354,23 +354,15 @@ TEST(Sync_AsyncWaitCancellation) ClientServerFixture fixture(dir, test_context); BowlOfStonesSemaphore bowl; - auto upload_completion_handler = [&](std::error_code ec) { - CHECK_EQUAL(util::error::operation_aborted, ec); - bowl.add_stone(); - }; - auto download_completion_handler = [&](std::error_code ec) { - CHECK_EQUAL(util::error::operation_aborted, ec); - bowl.add_stone(); - }; - auto sync_completion_handler = [&](std::error_code ec) { - CHECK_EQUAL(util::error::operation_aborted, ec); + auto completion_handler = [&](Status s) { + CHECK_EQUAL(s, ErrorCodes::OperationAborted); bowl.add_stone(); }; { Session session = fixture.make_bound_session(db, "/test"); - session.async_wait_for_upload_completion(upload_completion_handler); - session.async_wait_for_download_completion(download_completion_handler); - session.async_wait_for_sync_completion(sync_completion_handler); + session.async_wait_for_upload_completion(completion_handler); + session.async_wait_for_download_completion(completion_handler); + session.async_wait_for_sync_completion(completion_handler); // Destruction of session cancels wait operations } fixture.start(); @@ -551,7 +543,7 @@ TEST(Sync_WaitForSessionTerminations) // Note: Atomicity would not be needed if // Session::async_wait_for_download_completion() was assumed to work. std::atomic called{false}; - auto handler = [&](std::error_code) { + auto handler = [&](Status) { called = true; }; session.async_wait_for_download_completion(std::move(handler)); From ee757e6112bd97a3715d072a395b7e016ec34214 Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Thu, 13 Jul 2023 16:45:56 -0400 Subject: [PATCH 05/30] more test failures --- src/realm/object-store/audit.mm | 2 +- src/realm/object-store/sync/sync_session.cpp | 2 +- src/realm/sync/config.cpp | 2 +- src/realm/sync/config.hpp | 2 +- src/realm/sync/noinst/client_impl_base.cpp | 18 ++++++++++++-- src/realm/sync/protocol.cpp | 2 +- test/object-store/c_api/c_api.cpp | 4 ++- test/object-store/realm.cpp | 5 ++-- test/object-store/sync/app.cpp | 26 +++++++++++--------- 9 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/realm/object-store/audit.mm b/src/realm/object-store/audit.mm index 3b454075f20..40a41191fad 100644 --- a/src/realm/object-store/audit.mm +++ b/src/realm/object-store/audit.mm @@ -873,7 +873,7 @@ bool write_event(Timestamp timestamp, StringData activity, StringData event_type sync_config->error_handler = [error_handler = m_error_handler, weak_self = weak_from_this()](auto, SyncError error) { if (auto self = weak_self.lock()) { - self->m_logger->error("Events: Received sync error: %1 (code=%2)", error.to_status()); + self->m_logger->error("Events: Received sync error: %1", error.to_status()); } if (error_handler) { error_handler(error); diff --git a/src/realm/object-store/sync/sync_session.cpp b/src/realm/object-store/sync/sync_session.cpp index 7d4e9212140..18ae57c7243 100644 --- a/src/realm/object-store/sync/sync_session.cpp +++ b/src/realm/object-store/sync/sync_session.cpp @@ -249,7 +249,7 @@ void SyncSession::handle_bad_auth(const std::shared_ptr& user, Status } if (auto error_handler = config(&SyncConfig::error_handler)) { - auto user_facing_error = SyncError(std::move(status), true); + auto user_facing_error = SyncError({ErrorCodes::AuthError, status.reason()}, true); error_handler(shared_from_this(), std::move(user_facing_error)); } } diff --git a/src/realm/sync/config.cpp b/src/realm/sync/config.cpp index b310014273e..7bf63ce9953 100644 --- a/src/realm/sync/config.cpp +++ b/src/realm/sync/config.cpp @@ -44,7 +44,7 @@ using ProtocolError = realm::sync::ProtocolError; SyncError::SyncError(Status status, bool is_fatal, std::optional server_log, std::vector compensating_writes) - : RuntimeError(status.code(), format_sync_error_message(status, server_log)) + : Exception(status.code(), format_sync_error_message(status, server_log)) , is_fatal(is_fatal) , simple_message(std::string_view(reason()).substr(0, status.reason().size())) , compensating_writes_info(std::move(compensating_writes)) diff --git a/src/realm/sync/config.hpp b/src/realm/sync/config.hpp index ae3ab7409f5..f711f1eac0c 100644 --- a/src/realm/sync/config.hpp +++ b/src/realm/sync/config.hpp @@ -45,7 +45,7 @@ using port_type = std::uint_fast16_t; enum class ProtocolError; } // namespace sync -struct SyncError : public RuntimeError { +struct SyncError : public Exception { enum class ClientResetModeAllowed { DoNotClientReset, RecoveryPermitted, RecoveryNotPermitted }; bool is_fatal; diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp index 7d9bb1a03bb..b4a1bb60832 100644 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ b/src/realm/sync/noinst/client_impl_base.cpp @@ -2098,14 +2098,28 @@ void Session::send_json_error_message() ClientProtocol& protocol = m_conn.get_client_protocol(); OutputBuffer& out = m_conn.get_output_buffer(); session_ident_type session_ident = get_ident(); - auto protocol_error = ErrorCodes::RuntimeError; + auto protocol_error = ProtocolError::other_session_error; + switch (m_client_error->code()) { + case ErrorCodes::SyncProtocolInvariantFailed: + protocol_error = ProtocolError::bad_progress; + break; + case ErrorCodes::BadChangeset: + protocol_error = ProtocolError::bad_changeset; + break; + case ErrorCodes::LimitExceeded: + protocol_error = ProtocolError::bad_changeset_size; + break; + default: + protocol_error = ProtocolError::other_session_error; + break; + } auto message = m_client_error->what(); logger.info("Sending: ERROR \"%1\" (error_code=%2, session_ident=%3)", message, static_cast(protocol_error), session_ident); // Throws nlohmann::json error_body_json; - error_body_json["message"] = message; + error_body_json["message"] = util::format("%1", m_client_error->to_status()); protocol.make_json_error_message(out, session_ident, static_cast(protocol_error), error_body_json.dump()); // Throws m_conn.initiate_write_message(out, this); // Throws diff --git a/src/realm/sync/protocol.cpp b/src/realm/sync/protocol.cpp index 24e1e5c61ea..702876f4770 100644 --- a/src/realm/sync/protocol.cpp +++ b/src/realm/sync/protocol.cpp @@ -188,7 +188,7 @@ Status protocol_error_to_status(int raw_error_code, std::string_view msg) return ErrorCodes::WrongSyncType; case ProtocolError::session_closed: - REALM_UNREACHABLE(); + return ErrorCodes::ConnectionClosed; case ProtocolError::other_session_error: return ErrorCodes::RuntimeError; case ProtocolError::token_expired: diff --git a/test/object-store/c_api/c_api.cpp b/test/object-store/c_api/c_api.cpp index 1dcc9717005..215cc7f78b3 100644 --- a/test/object-store/c_api/c_api.cpp +++ b/test/object-store/c_api/c_api.cpp @@ -5117,7 +5117,9 @@ TEST_CASE("C API - async_open", "[c_api][sync]") { }); REQUIRE(userdata.called); REQUIRE(!userdata.realm_ref); - REQUIRE(userdata.error_message == "Bad user authentication (BIND)"); + REQUIRE(userdata.error.error == RLM_ERR_AUTH_ERROR); + REQUIRE(userdata.error_message == + "Unable to refresh the user access token: http error code considered fatal. Client Error: 403"); realm_release(task); realm_release(config); realm_release(sync_config); diff --git a/test/object-store/realm.cpp b/test/object-store/realm.cpp index 55f984c9ba2..964f73b5991 100644 --- a/test/object-store/realm.cpp +++ b/test/object-store/realm.cpp @@ -1189,8 +1189,9 @@ TEST_CASE("Get Realm using Async Open", "[asyncOpen]") { task->start([&](auto ref, auto error) { std::lock_guard lock(mutex); REQUIRE(error); - REQUIRE_EXCEPTION(std::rethrow_exception(error), HTTPError, - "http error code considered fatal. Client Error: 403"); + REQUIRE_EXCEPTION( + std::rethrow_exception(error), HTTPError, + "Unable to refresh the user access token: http error code considered fatal. Client Error: 403"); REQUIRE(!ref); called = true; }); diff --git a/test/object-store/sync/app.cpp b/test/object-store/sync/app.cpp index 729d735f818..3d7c065cf9f 100644 --- a/test/object-store/sync/app.cpp +++ b/test/object-store/sync/app.cpp @@ -2770,7 +2770,8 @@ TEST_CASE("app: sync integration", "[sync][app][baas]") { config.sync_config->error_handler = [&](std::shared_ptr, SyncError error) { sync_error_handler_called.store(true); REQUIRE(error.code() == ErrorCodes::AuthError); - REQUIRE(error.reason() == "Unable to refresh the user access token."); + REQUIRE_THAT(std::string{error.reason()}, + Catch::Matchers::StartsWith("Unable to refresh the user access token")); }; auto r = Realm::get_shared_realm(config); timed_wait_for([&] { @@ -2864,20 +2865,19 @@ TEST_CASE("app: sync integration", "[sync][app][baas]") { user->update_access_token(encode_fake_jwt("fake_access_token")); REQUIRE(!app_session.admin_api.verify_access_token(user->access_token(), app_session.server_app_id)); - std::atomic sync_error_handler_called{false}; - config.sync_config->error_handler = [&](std::shared_ptr, SyncError error) { - std::lock_guard lock(mtx); - sync_error_handler_called.store(true); - REQUIRE(error.code() == ErrorCodes::AuthError); - REQUIRE(error.reason() == "Unable to refresh the user access token."); - }; + auto [sync_error_promise, sync_error] = util::make_promise_future(); + config.sync_config->error_handler = + [promise = util::CopyablePromiseHolder(std::move(sync_error_promise))](std::shared_ptr, + SyncError error) mutable { + promise.get_promise().emplace_value(std::move(error)); + }; auto transport = static_cast(session.transport()); transport->block(); // don't let the token refresh happen until we're ready for it auto r = Realm::get_shared_realm(config); auto session = user->session_for_on_disk_path(config.path); REQUIRE(user->is_logged_in()); - REQUIRE(!sync_error_handler_called.load()); + REQUIRE(!sync_error.is_ready()); { std::atomic called{false}; session->wait_for_upload_completion([&](Status stat) { @@ -2892,9 +2892,11 @@ TEST_CASE("app: sync integration", "[sync][app][baas]") { std::lock_guard lock(mtx); REQUIRE(called); } - timed_wait_for([&] { - return sync_error_handler_called.load(); - }); + + auto sync_error_res = wait_for_future(std::move(sync_error)).get(); + REQUIRE(sync_error_res.code() == ErrorCodes::AuthError); + REQUIRE_THAT(std::string{sync_error_res.reason()}, + Catch::Matchers::StartsWith("Unable to refresh the user access token")); // the failed refresh logs out the user std::lock_guard lock(mtx); From 8371bac78e0dbe08f690a281ed89badde2559241 Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Thu, 13 Jul 2023 17:13:43 -0400 Subject: [PATCH 06/30] remove unused code --- test/object-store/sync/session/session.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/object-store/sync/session/session.cpp b/test/object-store/sync/session/session.cpp index 92808ed1404..29912bea0f5 100644 --- a/test/object-store/sync/session/session.cpp +++ b/test/object-store/sync/session/session.cpp @@ -413,9 +413,8 @@ TEST_CASE("sync: error handling", "[sync]") { final_error = std::move(error); }; - auto code = - static_cast(GENERATE(ProtocolError::bad_server_file_ident, ProtocolError::bad_client_file_ident, - ProtocolError::bad_server_version, ProtocolError::diverging_histories)); + auto code = static_cast(GENERATE(ProtocolError::bad_client_file_ident, ProtocolError::bad_server_version, + ProtocolError::diverging_histories)); sync::SessionErrorInfo initial_error{sync::protocol_error_to_status(code, "Something bad happened"), true}; initial_error.server_requests_action = ProtocolErrorInfo::Action::ClientReset; From 10e5041d0a5fc70d752e645fde629eb7cc8a5bb8 Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Thu, 13 Jul 2023 17:58:27 -0400 Subject: [PATCH 07/30] make IntegrationException unambiguous --- src/realm/sync/noinst/client_history_impl.cpp | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/realm/sync/noinst/client_history_impl.cpp b/src/realm/sync/noinst/client_history_impl.cpp index d12b37b476e..dca83861b9a 100644 --- a/src/realm/sync/noinst/client_history_impl.cpp +++ b/src/realm/sync/noinst/client_history_impl.cpp @@ -405,7 +405,7 @@ void ClientHistory::integrate_server_changesets( } catch (const BadChangesetError& e) { throw IntegrationException( - {ErrorCodes::BadChangeset, util::format("Failed to parse received changeset: %1", e.what())}); + Status{ErrorCodes::BadChangeset, util::format("Failed to parse received changeset: %1", e.what())}); } VersionID new_version{0, 0}; @@ -562,11 +562,11 @@ size_t ClientHistory::transform_and_apply_server_changesets(util::Span 0 && last_integrated_server_version < version_type(root.get_as_ref_or_tagged(s_progress_upload_server_version_iip).get_as_int())) { - throw IntegrationException({ErrorCodes::SyncProtocolInvariantFailed, - "last integrated server version of upload cursor cannot decrease"}); + throw IntegrationException(Status{ErrorCodes::SyncProtocolInvariantFailed, + "last integrated server version of upload cursor cannot decrease"}); } auto uploaded_bytes = std::uint_fast64_t(root.get_as_ref_or_tagged(s_progress_uploaded_bytes_iip).get_as_int()); From 043fe3fbbd1c71672e88001e2d3c5e112924ff90 Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Thu, 13 Jul 2023 18:02:20 -0400 Subject: [PATCH 08/30] even less unambiguous --- src/realm/sync/client.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/realm/sync/client.cpp b/src/realm/sync/client.cpp index 7893baf499d..06d9e1218f2 100644 --- a/src/realm/sync/client.cpp +++ b/src/realm/sync/client.cpp @@ -723,7 +723,7 @@ void SessionImpl::initiate_integrate_changesets(std::uint_fast64_t downloadable_ try { bool simulate_integration_error = (m_wrapper.m_simulate_integration_error && !changesets.empty()); if (simulate_integration_error) { - throw IntegrationException({ErrorCodes::BadChangeset, "simulated failure"}); + throw IntegrationException(Status{ErrorCodes::BadChangeset, "simulated failure"}); } version_type client_version; if (REALM_LIKELY(!get_client().is_dry_run())) { @@ -814,8 +814,8 @@ bool SessionImpl::process_flx_bootstrap_message(const SyncProgress& progress, Do } catch (const LogicError& ex) { if (ex.code() == ErrorCodes::LimitExceeded) { - IntegrationException ex( - {ErrorCodes::LimitExceeded, "bootstrap changeset too large to store in pending bootstrap store"}); + IntegrationException ex(Status{ErrorCodes::LimitExceeded, + "bootstrap changeset too large to store in pending bootstrap store"}); on_integration_failure(ex); return true; } @@ -895,7 +895,7 @@ void SessionImpl::process_pending_flx_bootstrap() bool simulate_integration_error = (m_wrapper.m_simulate_integration_error && !pending_batch.changesets.empty()); if (simulate_integration_error) { - throw IntegrationException({ErrorCodes::BadChangeset, "simulated failure"}); + throw IntegrationException(Status{ErrorCodes::BadChangeset, "simulated failure"}); } history.integrate_server_changesets( @@ -1690,7 +1690,6 @@ void SessionWrapper::on_upload_completion() while (!m_upload_completion_handlers.empty()) { auto handler = std::move(m_upload_completion_handlers.back()); m_upload_completion_handlers.pop_back(); - std::error_code ec; // Success handler(Status::OK()); // Throws } while (!m_sync_completion_handlers.empty()) { From f7a6b5f55431a45367093125223b84f01fed498a Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Wed, 19 Jul 2023 10:54:14 -0400 Subject: [PATCH 09/30] remove extrainfo part of status --- src/realm/exceptions.hpp | 28 ++------------ src/realm/impl/simulated_failure.hpp | 22 +---------- src/realm/status.cpp | 8 ++-- src/realm/status.hpp | 44 +++------------------- src/realm/sync/noinst/client_impl_base.cpp | 5 +-- test/test_sync.cpp | 16 +++----- 6 files changed, 23 insertions(+), 100 deletions(-) diff --git a/src/realm/exceptions.hpp b/src/realm/exceptions.hpp index 4aef8616fec..7521c40e77c 100644 --- a/src/realm/exceptions.hpp +++ b/src/realm/exceptions.hpp @@ -355,23 +355,6 @@ class FileAccessError : public RuntimeError { }; struct SystemError : RuntimeError { - class ExtraInfo : public Status::ExtraInfo { - public: - ExtraInfo(std::error_code ec) - : ec(ec) - { - } - - std::error_code ec; - }; - - static Status make_status(std::error_code err, std::string_view msg, bool msg_is_prefix) - { - return Status(ErrorCodes::SystemError, - msg_is_prefix ? util::format("%1: %2 (%3)", msg, err.message(), err.value()) : msg, - std::make_unique(err)); - } - SystemError(std::error_code err, std::string_view msg) : RuntimeError(make_status(err, msg, false)) { @@ -384,14 +367,11 @@ struct SystemError : RuntimeError { ~SystemError() noexcept override; - std::error_code get_system_error() const - { - return to_status().get_extra_info().ec; - } - - const std::error_category& get_category() const +private: + static Status make_status(std::error_code err, std::string_view msg, bool msg_is_prefix) { - return get_system_error().category(); + return Status(ErrorCodes::SystemError, + msg_is_prefix ? util::format("%1: %2 (%3)", msg, err.message(), err.value()) : msg); } }; diff --git a/src/realm/impl/simulated_failure.hpp b/src/realm/impl/simulated_failure.hpp index e983f47693f..a506d4b2303 100644 --- a/src/realm/impl/simulated_failure.hpp +++ b/src/realm/impl/simulated_failure.hpp @@ -46,16 +46,6 @@ class SimulatedFailure : public RuntimeError { _num_failure_types }; - class ExtraInfo : public Status::ExtraInfo { - public: - ExtraInfo(FailureType failure_type) - : failure_type(failure_type) - { - } - - FailureType failure_type; - }; - class OneShotPrimeGuard; class RandomPrimeGuard; @@ -76,9 +66,6 @@ class SimulatedFailure : public RuntimeError { /// not defined, this function always return false. static bool check_trigger(FailureType) noexcept; - static Status make_simulated_failure_status(FailureType failure_type, - ErrorCodes::Error error_code = ErrorCodes::RuntimeError); - /// Throws SimulatedFailure if check_trigger() returns true. The exception /// will be constructed with an error code equal to /// `make_error_code(failure_type)`. @@ -195,11 +182,6 @@ inline bool SimulatedFailure::check_trigger(FailureType failure_type) noexcept #endif } -inline Status SimulatedFailure::make_simulated_failure_status(FailureType failure_type, ErrorCodes::Error error_code) -{ - return Status(error_code, "SimulatedFailure", std::make_unique(failure_type)); -} - inline void SimulatedFailure::trigger(FailureType failure_type) { if (check_trigger(failure_type)) @@ -224,8 +206,8 @@ inline void SimulatedFailure::set_thread_local(bool tl) #endif } -inline SimulatedFailure::SimulatedFailure(FailureType type) - : RuntimeError(Status{ErrorCodes::RuntimeError, "SimulatedFailure", std::make_unique(type)}) +inline SimulatedFailure::SimulatedFailure(FailureType) + : RuntimeError(Status{ErrorCodes::RuntimeError, "SimulatedFailure"}) { } diff --git a/src/realm/status.cpp b/src/realm/status.cpp index 8aa4e9f0e5d..f8e860d6840 100644 --- a/src/realm/status.cpp +++ b/src/realm/status.cpp @@ -22,22 +22,20 @@ namespace realm { -Status::ErrorInfo::ErrorInfo(ErrorCodes::Error code, std::string&& reason, std::unique_ptr&& extra_info) +Status::ErrorInfo::ErrorInfo(ErrorCodes::Error code, std::string&& reason) : m_refs(0) , m_code(code) , m_reason(std::move(reason)) - , m_extra_info(std::move(extra_info)) { } -util::bind_ptr Status::ErrorInfo::create(ErrorCodes::Error code, std::string&& reason, - std::unique_ptr&& extra_info) +util::bind_ptr Status::ErrorInfo::create(ErrorCodes::Error code, std::string&& reason) { // OK status should be created by calling Status::OK() - which is a special case that doesn't allocate // anything. REALM_ASSERT(code != ErrorCodes::OK); - return util::bind_ptr(new ErrorInfo(code, std::move(reason), std::move(extra_info))); + return util::bind_ptr(new ErrorInfo(code, std::move(reason))); } std::ostream& operator<<(std::ostream& out, const Status& val) diff --git a/src/realm/status.hpp b/src/realm/status.hpp index a3ae3df37b4..adea6364038 100644 --- a/src/realm/status.hpp +++ b/src/realm/status.hpp @@ -31,11 +31,6 @@ namespace realm { class REALM_NODISCARD Status { public: - class ExtraInfo { - public: - virtual ~ExtraInfo() = default; - }; - /* * This is the best way to construct a Status that represents a non-error condition. */ @@ -45,13 +40,13 @@ class REALM_NODISCARD Status { * You can construct a Status from anything that can construct a std::string_view. */ template , int> = 0> - Status(ErrorCodes::Error code, Reason&& reason, std::unique_ptr extra_info = nullptr) - : m_error(ErrorInfo::create(code, std::string{std::string_view{reason}}, std::move(extra_info))) + Status(ErrorCodes::Error code, Reason&& reason) + : m_error(ErrorInfo::create(code, std::string{std::string_view{reason}})) { } - Status(ErrorCodes::Error code, std::string&& reason, std::unique_ptr extra_info = nullptr) - : m_error(ErrorInfo::create(code, std::move(reason), std::move(extra_info))) + Status(ErrorCodes::Error code, std::string&& reason) + : m_error(ErrorInfo::create(code, std::move(reason))) { } @@ -69,36 +64,12 @@ class REALM_NODISCARD Status { inline ErrorCodes::Error code() const noexcept; inline std::string_view code_string() const noexcept; - bool is_same_as(const Status& other) const noexcept - { - return m_error == other.m_error; - } - /* * This class is marked nodiscard so that we always handle errors. If there is a place where we need * to explicitly ignore an error, you can call this function, which does nothing, to satisfy the compiler. */ void ignore() const noexcept {} - template - bool has_extra_info() const noexcept - { - if (!m_error || !m_error->m_extra_info) { - return false; - } - - return dynamic_cast(m_error->m_extra_info.get()) != nullptr; - } - - template - const T& get_extra_info() const noexcept - { - REALM_ASSERT(m_error); - REALM_ASSERT(m_error->m_extra_info); - - return *dynamic_cast(m_error->m_extra_info.get()); - } - private: Status() = default; @@ -107,10 +78,7 @@ class REALM_NODISCARD Status { const ErrorCodes::Error m_code; const std::string m_reason; - std::unique_ptr m_extra_info; - - static util::bind_ptr create(ErrorCodes::Error code, std::string&& reason, - std::unique_ptr&& extra_info); + static util::bind_ptr create(ErrorCodes::Error code, std::string&& reason); protected: template @@ -129,7 +97,7 @@ class REALM_NODISCARD Status { } private: - ErrorInfo(ErrorCodes::Error code, std::string&& reason, std::unique_ptr&& extra_info); + ErrorInfo(ErrorCodes::Error code, std::string&& reason); }; util::bind_ptr m_error = {}; diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp index f23f76b99ab..a14666155e7 100644 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ b/src/realm/sync/noinst/client_impl_base.cpp @@ -489,9 +489,8 @@ bool Connection::websocket_binary_message_received(util::Span data) using sf = SimulatedFailure; if (sf::check_trigger(sf::sync_client__read_head)) { close_due_to_client_side_error( - sf::make_simulated_failure_status(SimulatedFailure::FailureType::sync_client__read_head, - ErrorCodes::ConnectionClosed), - IsFatal{false}, ConnectionTerminationReason::read_or_write_error); // Throws + {ErrorCodes::RuntimeError, "Simulated failure during sync client websocket read"}, IsFatal{false}, + ConnectionTerminationReason::read_or_write_error); return bool(m_websocket); } diff --git a/test/test_sync.cpp b/test/test_sync.cpp index 5af39fd98f3..16ab92239c4 100644 --- a/test/test_sync.cpp +++ b/test/test_sync.cpp @@ -1473,10 +1473,8 @@ TEST(Sync_ReadFailureSimulation) ClientServerFixture fixture(server_dir, test_context); fixture.set_client_side_error_rate(1, 1); // 100% chance of failure auto error_handler = [&](Status status, bool is_fatal) { - using sf = _impl::SimulatedFailure; - CHECK(status.has_extra_info()); - CHECK_EQUAL(status.get_extra_info().failure_type, - sf::FailureType::sync_client__read_head); + CHECK_EQUAL(status, ErrorCodes::RuntimeError); + CHECK_EQUAL(status.reason(), "Simulated failure during sync client websocket read"); CHECK_NOT(is_fatal); client_side_read_did_fail = true; fixture.stop(); @@ -1503,12 +1501,10 @@ TEST(Sync_FailingReadsOnClientSide) TEST_DIR(dir); ClientServerFixture fixture{dir, test_context}; fixture.set_client_side_error_rate(5, 100); // 5% chance of failure - auto error_handler = [&](Status status, bool) { - using sf = _impl::SimulatedFailure; - if (CHECK(status.has_extra_info())) { - CHECK_EQUAL(status.get_extra_info().failure_type, - sf::FailureType::sync_client__read_head); - CHECK_EQUAL(status, ErrorCodes::ConnectionClosed); + auto error_handler = [&](Status status, bool is_fatal) { + if (CHECK_EQUAL(status.reason(), "Simulated failure during sync client websocket read")) { + CHECK_EQUAL(status, ErrorCodes::RuntimeError); + CHECK_NOT(is_fatal); fixture.cancel_reconnect_delay(); } }; From 104ca6d743e3e06c59d0f9ea84f5a06b9073de23 Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Sun, 30 Jul 2023 15:49:56 -0400 Subject: [PATCH 10/30] Replace ClientError error_code with ErrorCodes/Status --- src/realm/error_codes.h | 34 -- src/realm/object-store/c_api/sync.cpp | 17 +- src/realm/object-store/sync/sync_session.cpp | 66 +--- src/realm/sync/client.cpp | 186 +-------- src/realm/sync/client.hpp | 2 - src/realm/sync/client_base.hpp | 60 --- src/realm/sync/config.cpp | 2 +- src/realm/sync/noinst/client_history_impl.cpp | 34 +- src/realm/sync/noinst/client_history_impl.hpp | 17 +- src/realm/sync/noinst/client_impl_base.cpp | 360 +++++++++--------- src/realm/sync/noinst/client_impl_base.hpp | 36 +- src/realm/sync/noinst/protocol_codec.hpp | 16 +- src/realm/sync/noinst/server/server.cpp | 5 +- src/realm/sync/protocol.cpp | 5 + src/realm/sync/protocol.hpp | 2 + test/object-store/c_api/c_api.cpp | 14 +- test/object-store/sync/app.cpp | 2 +- test/object-store/sync/client_reset.cpp | 5 +- test/object-store/sync/flx_sync.cpp | 24 +- test/sync_fixtures.hpp | 18 +- test/test_client_reset.cpp | 3 +- test/test_sync.cpp | 68 ++-- 22 files changed, 331 insertions(+), 645 deletions(-) diff --git a/src/realm/error_codes.h b/src/realm/error_codes.h index 6eb2b2b6ae7..cb2dc1a0ddd 100644 --- a/src/realm/error_codes.h +++ b/src/realm/error_codes.h @@ -209,40 +209,6 @@ typedef enum realm_errno { RLM_ERR_UNKNOWN = 2000000 /* Should not be used in code */ } realm_errno_e; -typedef enum realm_sync_errno_client { - RLM_SYNC_ERR_CLIENT_CONNECTION_CLOSED = 100, - RLM_SYNC_ERR_CLIENT_UNKNOWN_MESSAGE = 101, - RLM_SYNC_ERR_CLIENT_BAD_SYNTAX = 102, - RLM_SYNC_ERR_CLIENT_LIMITS_EXCEEDED = 103, - RLM_SYNC_ERR_CLIENT_BAD_SESSION_IDENT = 104, - RLM_SYNC_ERR_CLIENT_BAD_MESSAGE_ORDER = 105, - RLM_SYNC_ERR_CLIENT_BAD_CLIENT_FILE_IDENT = 106, - RLM_SYNC_ERR_CLIENT_BAD_PROGRESS = 107, - RLM_SYNC_ERR_CLIENT_BAD_CHANGESET_HEADER_SYNTAX = 108, - RLM_SYNC_ERR_CLIENT_BAD_CHANGESET_SIZE = 109, - RLM_SYNC_ERR_CLIENT_BAD_ORIGIN_FILE_IDENT = 110, - RLM_SYNC_ERR_CLIENT_BAD_SERVER_VERSION = 111, - RLM_SYNC_ERR_CLIENT_BAD_CHANGESET = 112, - RLM_SYNC_ERR_CLIENT_BAD_REQUEST_IDENT = 113, - RLM_SYNC_ERR_CLIENT_BAD_ERROR_CODE = 114, - RLM_SYNC_ERR_CLIENT_BAD_COMPRESSION = 115, - RLM_SYNC_ERR_CLIENT_BAD_CLIENT_VERSION = 116, - RLM_SYNC_ERR_CLIENT_SSL_SERVER_CERT_REJECTED = 117, - RLM_SYNC_ERR_CLIENT_PONG_TIMEOUT = 118, - RLM_SYNC_ERR_CLIENT_BAD_CLIENT_FILE_IDENT_SALT = 119, - RLM_SYNC_ERR_CLIENT_BAD_FILE_IDENT = 120, - RLM_SYNC_ERR_CLIENT_CONNECT_TIMEOUT = 121, - RLM_SYNC_ERR_CLIENT_BAD_TIMESTAMP = 122, - RLM_SYNC_ERR_CLIENT_BAD_PROTOCOL_FROM_SERVER = 123, - RLM_SYNC_ERR_CLIENT_CLIENT_TOO_OLD_FOR_SERVER = 124, - RLM_SYNC_ERR_CLIENT_CLIENT_TOO_NEW_FOR_SERVER = 125, - RLM_SYNC_ERR_CLIENT_PROTOCOL_MISMATCH = 126, - RLM_SYNC_ERR_CLIENT_BAD_STATE_MESSAGE = 127, - RLM_SYNC_ERR_CLIENT_MISSING_PROTOCOL_FEATURE = 128, - RLM_SYNC_ERR_CLIENT_HTTP_TUNNEL_FAILED = 131, - RLM_SYNC_ERR_CLIENT_AUTO_CLIENT_RESET_FAILURE = 132, -} realm_sync_errno_client_e; - typedef enum realm_sync_errno_connection { RLM_SYNC_ERR_CONNECTION_CONNECTION_CLOSED = 100, RLM_SYNC_ERR_CONNECTION_OTHER_ERROR = 101, diff --git a/src/realm/object-store/c_api/sync.cpp b/src/realm/object-store/c_api/sync.cpp index cf72dd49a8e..945d5fbb1e8 100644 --- a/src/realm/object-store/c_api/sync.cpp +++ b/src/realm/object-store/c_api/sync.cpp @@ -128,7 +128,9 @@ realm_sync_error_code_t to_capi(const Status& status, std::string& message) auto error_code = status.get_std_error_code(); const std::error_category& category = error_code.category(); - if (category == realm::sync::client_error_category()) { + if (status != ErrorCodes::SystemError) { + // TODO this will all go away when the unify error handling project is done, but for now just treat the + // ErrorCodes enum as client errors. ret.category = RLM_SYNC_ERROR_CATEGORY_CLIENT; } else if (category == realm::sync::protocol_error_category()) { @@ -149,12 +151,17 @@ realm_sync_error_code_t to_capi(const Status& status, std::string& message) ret.category = RLM_SYNC_ERROR_CATEGORY_UNKNOWN; } - ret.value = error_code.value(); - message = error_code.message(); // pass the string to the caller for lifetime purposes + if (status == ErrorCodes::SystemError) { + ret.value = error_code.value(); + message = error_code.message(); + } + else { + ret.value = status.code(); + message = status.reason(); + } ret.message = message.c_str(); ret.category_name = category.name(); - return ret; } @@ -163,7 +170,7 @@ void sync_error_to_error_code(const realm_sync_error_code_t& sync_error_code, st if (error_code_out) { const realm_sync_error_category_e category = sync_error_code.category; if (category == RLM_SYNC_ERROR_CATEGORY_CLIENT) { - error_code_out->assign(sync_error_code.value, realm::sync::client_error_category()); + error_code_out->assign(sync_error_code.value, std::generic_category()); } else if (category == RLM_SYNC_ERROR_CATEGORY_SESSION || category == RLM_SYNC_ERROR_CATEGORY_CONNECTION) { error_code_out->assign(sync_error_code.value, realm::sync::protocol_error_category()); diff --git a/src/realm/object-store/sync/sync_session.cpp b/src/realm/object-store/sync/sync_session.cpp index 37b67f177ca..52e99809074 100644 --- a/src/realm/object-store/sync/sync_session.cpp +++ b/src/realm/object-store/sync/sync_session.cpp @@ -223,7 +223,7 @@ void SyncSession::do_become_inactive(util::CheckedUniqueLock lock, Status status m_connection_change_notifier.invoke_callbacks(old_state, connection_state()); } - if (!status.get_std_error_code()) + if (status.is_ok()) status = Status(ErrorCodes::OperationAborted, "Sync session became inactive"); // Inform any queued-up completion handlers that they were cancelled. @@ -581,7 +581,7 @@ void SyncSession::handle_fresh_realm_downloaded(DBRef db, Status status, const bool try_again = false; sync::SessionErrorInfo synthetic( - Status{make_error_code(sync::Client::Error::auto_client_reset_failure), + Status{ErrorCodes::AutoClientResetFailed, util::format("A fatal error occurred during client reset: '%1'", status.reason())}, try_again); handle_error(synthetic); @@ -639,22 +639,19 @@ void SyncSession::handle_error(sync::SessionErrorInfo error) bool log_out_user = false; bool unrecognized_by_client = false; - if (error_code == make_error_code(sync::Client::Error::auto_client_reset_failure)) { + if (error.status == ErrorCodes::AutoClientResetFailed) { // At this point, automatic recovery has been attempted but it failed. // Fallback to a manual reset and let the user try to handle it. next_state = NextStateAfterError::inactive; delete_file = ShouldBackup::yes; } - else if (error_code.category() == realm::sync::protocol_error_category()) { + else if (error_code == realm::sync::ProtocolError::bad_authentication) { + next_state = NextStateAfterError::inactive; + log_out_user = true; + } + else if (error.server_requests_action != sync::ProtocolErrorInfo::Action::NoAction) { switch (error.server_requests_action) { case sync::ProtocolErrorInfo::Action::NoAction: - // Although a protocol error, this is not sent by the server. - // Therefore, there is no action. - if (error_code == realm::sync::ProtocolError::bad_authentication) { - next_state = NextStateAfterError::inactive; - log_out_user = true; - break; - } REALM_UNREACHABLE(); // This is not sent by the MongoDB server case sync::ProtocolErrorInfo::Action::ApplicationBug: [[fallthrough]]; @@ -713,48 +710,6 @@ void SyncSession::handle_error(sync::SessionErrorInfo error) return; } } - else if (error_code.category() == realm::sync::client_error_category()) { - using ClientError = realm::sync::ClientError; - switch (static_cast(error_code.value())) { - case ClientError::connection_closed: - case ClientError::pong_timeout: - // Not real errors, don't need to be reported to the SDK. - return; - case ClientError::bad_changeset: - case ClientError::bad_changeset_header_syntax: - case ClientError::bad_changeset_size: - case ClientError::bad_client_file_ident: - case ClientError::bad_client_file_ident_salt: - case ClientError::bad_client_version: - case ClientError::bad_compression: - case ClientError::bad_error_code: - case ClientError::bad_file_ident: - case ClientError::bad_message_order: - case ClientError::bad_origin_file_ident: - case ClientError::bad_progress: - case ClientError::bad_protocol_from_server: - case ClientError::bad_request_ident: - case ClientError::bad_server_version: - case ClientError::bad_session_ident: - case ClientError::bad_state_message: - case ClientError::bad_syntax: - case ClientError::bad_timestamp: - case ClientError::client_too_new_for_server: - case ClientError::client_too_old_for_server: - case ClientError::connect_timeout: - case ClientError::limits_exceeded: - case ClientError::protocol_mismatch: - case ClientError::ssl_server_cert_rejected: - case ClientError::missing_protocol_feature: - case ClientError::unknown_message: - case ClientError::http_tunnel_failed: - case ClientError::auto_client_reset_failure: - // Don't do anything special for these errors. - // Future functionality may require special-case handling for existing - // errors, or newly introduced error codes. - break; - } - } else if (error_code.category() == sync::websocket::websocket_error_category()) { using WebSocketError = sync::websocket::WebSocketError; auto websocket_error = static_cast(error_code.value()); @@ -801,7 +756,7 @@ void SyncSession::handle_error(sync::SessionErrorInfo error) update_error_and_mark_file_for_deletion(sync_error, *delete_file); if (m_state == State::Dying && error.is_fatal()) { - become_inactive(std::move(lock)); + become_inactive(std::move(lock), error.status); return; } @@ -822,8 +777,7 @@ void SyncSession::handle_error(sync::SessionErrorInfo error) break; } case NextStateAfterError::error: { - auto error = sync_error.status; - cancel_pending_waits(std::move(lock), error); + cancel_pending_waits(std::move(lock), sync_error.status); break; } } diff --git a/src/realm/sync/client.cpp b/src/realm/sync/client.cpp index b961aec3ea7..8a4287dcd74 100644 --- a/src/realm/sync/client.cpp +++ b/src/realm/sync/client.cpp @@ -40,96 +40,6 @@ using connection_ident_type = std::int_fast64_t; using ProxyConfig = SyncConfig::ProxyConfig; // clang-format on - -const char* get_error_message(ClientError error_code) -{ - switch (error_code) { - case ClientError::connection_closed: - return "Connection closed (no error)"; - case ClientError::unknown_message: - return "Unknown type of input message"; - case ClientError::bad_syntax: - return "Bad syntax in input message head"; - case ClientError::limits_exceeded: - return "Limits exceeded in input message"; - case ClientError::bad_session_ident: - return "Bad session identifier in input message"; - case ClientError::bad_message_order: - return "Bad input message order"; - case ClientError::bad_client_file_ident: - return "Bad client file identifier (IDENT)"; - case ClientError::bad_progress: - return "Bad progress information (DOWNLOAD)"; - case ClientError::bad_changeset_header_syntax: - return "Bad progress information (DOWNLOAD)"; - case ClientError::bad_changeset_size: - return "Bad changeset size in changeset header (DOWNLOAD)"; - case ClientError::bad_origin_file_ident: - return "Bad origin file identifier in changeset header (DOWNLOAD)"; - case ClientError::bad_server_version: - return "Bad server version in changeset header (DOWNLOAD)"; - case ClientError::bad_changeset: - return "Bad changeset (DOWNLOAD)"; - case ClientError::bad_request_ident: - return "Bad request identifier (MARK)"; - case ClientError::bad_error_code: - return "Bad error code (ERROR)"; - case ClientError::bad_compression: - return "Bad compression (DOWNLOAD)"; - case ClientError::bad_client_version: - return "Bad last integrated client version in changeset header (DOWNLOAD)"; - case ClientError::ssl_server_cert_rejected: - return "SSL server certificate rejected"; - case ClientError::pong_timeout: - return "Timeout on reception of PONG respone message"; - case ClientError::bad_client_file_ident_salt: - return "Bad client file identifier salt (IDENT)"; - case ClientError::bad_file_ident: - return "Bad file identifier (ALLOC)"; - case ClientError::connect_timeout: - return "Sync connection was not fully established in time"; - case ClientError::bad_timestamp: - return "Bad timestamp (PONG)"; - case ClientError::bad_protocol_from_server: - return "Bad or missing protocol version information from server"; - case ClientError::client_too_old_for_server: - return "Protocol version negotiation failed: Client is too old for server"; - case ClientError::client_too_new_for_server: - return "Protocol version negotiation failed: Client is too new for server"; - case ClientError::protocol_mismatch: - return ("Protocol version negotiation failed: No version supported by both " - "client and server"); - case ClientError::bad_state_message: - return "Bad state message (STATE)"; - case ClientError::missing_protocol_feature: - return "Requested feature missing in negotiated protocol version"; - case ClientError::http_tunnel_failed: - return "Failure to establish HTTP tunnel with configured proxy"; - case ClientError::auto_client_reset_failure: - return "Automatic recovery from client reset failed"; - } - return nullptr; -} - - -class ErrorCategoryImpl : public std::error_category { -public: - const char* name() const noexcept override final - { - return "realm::sync::ClientError"; - } - std::string message(int error_code) const override final - { - const char* msg = get_error_message(ClientError(error_code)); - if (!msg) - msg = "Unknown error"; - std::string msg_2{msg}; // Throws (copy) - return msg_2; - } -}; - -ErrorCategoryImpl g_error_category; - } // unnamed namespace @@ -814,7 +724,7 @@ void SessionImpl::initiate_integrate_changesets(std::uint_fast64_t downloadable_ try { bool simulate_integration_error = (m_wrapper.m_simulate_integration_error && !changesets.empty()); if (simulate_integration_error) { - throw IntegrationException(ClientError::bad_changeset, "simulated failure"); + throw IntegrationException(ErrorCodes::BadChangeset, "simulated failure", ProtocolError::bad_changeset); } version_type client_version; if (REALM_LIKELY(!get_client().is_dry_run())) { @@ -905,8 +815,9 @@ bool SessionImpl::process_flx_bootstrap_message(const SyncProgress& progress, Do } catch (const LogicError& ex) { if (ex.code() == ErrorCodes::LimitExceeded) { - IntegrationException ex(ClientError::bad_changeset_size, - "bootstrap changeset too large to store in pending bootstrap store"); + IntegrationException ex(ErrorCodes::LimitExceeded, + "bootstrap changeset too large to store in pending bootstrap store", + ProtocolError::bad_changeset_size); on_integration_failure(ex); return true; } @@ -986,7 +897,7 @@ void SessionImpl::process_pending_flx_bootstrap() bool simulate_integration_error = (m_wrapper.m_simulate_integration_error && !pending_batch.changesets.empty()); if (simulate_integration_error) { - throw IntegrationException(ClientError::bad_changeset, "simulated failure"); + throw IntegrationException(ErrorCodes::BadChangeset, "simulated failure", ProtocolError::bad_changeset); } history.integrate_server_changesets( @@ -1089,12 +1000,11 @@ SyncClientHookAction SessionImpl::call_debug_hook(const SyncClientHookData& data auto action = m_wrapper.m_debug_hook(data); switch (action) { case realm::SyncClientHookAction::SuspendWithRetryableError: { - SessionErrorInfo err_info( - Status{make_error_code(ProtocolError::other_session_error), "hook requested error"}, true); + SessionErrorInfo err_info(Status{ErrorCodes::RuntimeError, "hook requested error"}, true); err_info.server_requests_action = ProtocolErrorInfo::Action::Transient; auto err_processing_err = receive_error_message(err_info); - REALM_ASSERT_EX(!err_processing_err, err_processing_err.message()); + REALM_ASSERT_EX(err_processing_err.is_ok(), err_processing_err); return SyncClientHookAction::EarlyReturn; } case realm::SyncClientHookAction::TriggerReconnect: { @@ -2231,88 +2141,6 @@ std::string Session::get_appservices_connection_id() return m_impl->get_appservices_connection_id(); } -const std::error_category& client_error_category() noexcept -{ - return g_error_category; -} - -std::error_code make_error_code(ClientError error_code) noexcept -{ - return std::error_code{int(error_code), g_error_category}; -} - -ProtocolError client_error_to_protocol_error(ClientError error_code) -{ - switch (error_code) { - case ClientError::bad_changeset: - return ProtocolError::bad_changeset; - case ClientError::bad_progress: - return ProtocolError::bad_progress; - case ClientError::bad_changeset_size: - return ProtocolError::bad_changeset_size; - case ClientError::connection_closed: - return ProtocolError::connection_closed; - case ClientError::unknown_message: - return ProtocolError::unknown_message; - case ClientError::bad_syntax: - return ProtocolError::bad_syntax; - case ClientError::limits_exceeded: - return ProtocolError::limits_exceeded; - case ClientError::bad_session_ident: - return ProtocolError::bad_session_ident; - case ClientError::bad_message_order: - return ProtocolError::bad_message_order; - case ClientError::bad_client_file_ident: - return ProtocolError::bad_client_file_ident; - case ClientError::bad_changeset_header_syntax: - return ProtocolError::bad_changeset_header_syntax; - case ClientError::bad_origin_file_ident: - return ProtocolError::bad_origin_file_ident; - case ClientError::bad_server_version: - return ProtocolError::bad_server_version; - case ClientError::bad_client_version: - return ProtocolError::bad_client_version; - - case ClientError::bad_error_code: - [[fallthrough]]; - case ClientError::bad_request_ident: - [[fallthrough]]; - case ClientError::bad_compression: - [[fallthrough]]; - case ClientError::ssl_server_cert_rejected: - [[fallthrough]]; - case ClientError::bad_client_file_ident_salt: - [[fallthrough]]; - case ClientError::bad_file_ident: - [[fallthrough]]; - case ClientError::connect_timeout: - [[fallthrough]]; - case ClientError::bad_timestamp: - [[fallthrough]]; - case ClientError::bad_protocol_from_server: - [[fallthrough]]; - case ClientError::client_too_old_for_server: - [[fallthrough]]; - case ClientError::client_too_new_for_server: - [[fallthrough]]; - case ClientError::protocol_mismatch: - [[fallthrough]]; - case ClientError::missing_protocol_feature: - [[fallthrough]]; - case ClientError::http_tunnel_failed: - return ProtocolError::other_error; - - case ClientError::auto_client_reset_failure: - [[fallthrough]]; - case ClientError::pong_timeout: - [[fallthrough]]; - case ClientError::bad_state_message: - return ProtocolError::other_session_error; - } - - return ProtocolError::other_error; -} - std::ostream& operator<<(std::ostream& os, ProxyConfig::Type proxyType) { switch (proxyType) { diff --git a/src/realm/sync/client.hpp b/src/realm/sync/client.hpp index d926382c723..71fe528399b 100644 --- a/src/realm/sync/client.hpp +++ b/src/realm/sync/client.hpp @@ -23,8 +23,6 @@ class Client { public: using port_type = sync::port_type; - using Error = ClientError; - static constexpr milliseconds_type default_connect_timeout = sync::default_connect_timeout; static constexpr milliseconds_type default_connection_linger_time = sync::default_connection_linger_time; static constexpr milliseconds_type default_ping_keepalive_period = sync::default_ping_keepalive_period; diff --git a/src/realm/sync/client_base.hpp b/src/realm/sync/client_base.hpp index 6ddfd2bb18f..e3cb0f59096 100644 --- a/src/realm/sync/client_base.hpp +++ b/src/realm/sync/client_base.hpp @@ -66,66 +66,6 @@ struct ClientReset { util::UniqueFunction notify_after_client_reset; }; -/// \brief Protocol errors discovered by the client. -/// -/// These errors will terminate the network connection (disconnect all sessions -/// associated with the affected connection), and the error will be reported to -/// the application via the connection state change listeners of the affected -/// sessions. -enum class ClientError { - // clang-format off - connection_closed = RLM_SYNC_ERR_CLIENT_CONNECTION_CLOSED , ///< Connection closed (no error) - unknown_message = RLM_SYNC_ERR_CLIENT_UNKNOWN_MESSAGE , ///< Unknown type of input message - bad_syntax = RLM_SYNC_ERR_CLIENT_BAD_SYNTAX , ///< Bad syntax in input message head - limits_exceeded = RLM_SYNC_ERR_CLIENT_LIMITS_EXCEEDED , ///< Limits exceeded in input message - bad_session_ident = RLM_SYNC_ERR_CLIENT_BAD_SESSION_IDENT , ///< Bad session identifier in input message - bad_message_order = RLM_SYNC_ERR_CLIENT_BAD_MESSAGE_ORDER , ///< Bad input message order - bad_client_file_ident = RLM_SYNC_ERR_CLIENT_BAD_CLIENT_FILE_IDENT , ///< Bad client file identifier (IDENT) - bad_progress = RLM_SYNC_ERR_CLIENT_BAD_PROGRESS , ///< Bad progress information (DOWNLOAD) - bad_changeset_header_syntax = RLM_SYNC_ERR_CLIENT_BAD_CHANGESET_HEADER_SYNTAX, ///< Bad syntax in changeset header (DOWNLOAD) - bad_changeset_size = RLM_SYNC_ERR_CLIENT_BAD_CHANGESET_SIZE , ///< Bad changeset size in changeset header (DOWNLOAD) - bad_origin_file_ident = RLM_SYNC_ERR_CLIENT_BAD_ORIGIN_FILE_IDENT , ///< Bad origin file identifier in changeset header (DOWNLOAD) - bad_server_version = RLM_SYNC_ERR_CLIENT_BAD_SERVER_VERSION , ///< Bad server version in changeset header (DOWNLOAD) - bad_changeset = RLM_SYNC_ERR_CLIENT_BAD_CHANGESET , ///< Bad changeset (DOWNLOAD) - bad_request_ident = RLM_SYNC_ERR_CLIENT_BAD_REQUEST_IDENT , ///< Bad request identifier (MARK) - bad_error_code = RLM_SYNC_ERR_CLIENT_BAD_ERROR_CODE , ///< Bad error code (ERROR), - bad_compression = RLM_SYNC_ERR_CLIENT_BAD_COMPRESSION , ///< Bad compression (DOWNLOAD) - bad_client_version = RLM_SYNC_ERR_CLIENT_BAD_CLIENT_VERSION , ///< Bad last integrated client version in changeset header (DOWNLOAD) - ssl_server_cert_rejected = RLM_SYNC_ERR_CLIENT_SSL_SERVER_CERT_REJECTED , ///< SSL server certificate rejected - pong_timeout = RLM_SYNC_ERR_CLIENT_PONG_TIMEOUT , ///< Timeout on reception of PONG respone message - bad_client_file_ident_salt = RLM_SYNC_ERR_CLIENT_BAD_CLIENT_FILE_IDENT_SALT , ///< Bad client file identifier salt (IDENT) - bad_file_ident = RLM_SYNC_ERR_CLIENT_BAD_FILE_IDENT , ///< Bad file identifier (ALLOC) - connect_timeout = RLM_SYNC_ERR_CLIENT_CONNECT_TIMEOUT , ///< Sync connection was not fully established in time - bad_timestamp = RLM_SYNC_ERR_CLIENT_BAD_TIMESTAMP , ///< Bad timestamp (PONG) - bad_protocol_from_server = RLM_SYNC_ERR_CLIENT_BAD_PROTOCOL_FROM_SERVER , ///< Bad or missing protocol version information from server - client_too_old_for_server = RLM_SYNC_ERR_CLIENT_CLIENT_TOO_OLD_FOR_SERVER , ///< Protocol version negotiation failed: Client is too old for server - client_too_new_for_server = RLM_SYNC_ERR_CLIENT_CLIENT_TOO_NEW_FOR_SERVER , ///< Protocol version negotiation failed: Client is too new for server - protocol_mismatch = RLM_SYNC_ERR_CLIENT_PROTOCOL_MISMATCH , ///< Protocol version negotiation failed: No version supported by both client and server - bad_state_message = RLM_SYNC_ERR_CLIENT_BAD_STATE_MESSAGE , ///< Bad values in state message (STATE) - missing_protocol_feature = RLM_SYNC_ERR_CLIENT_MISSING_PROTOCOL_FEATURE , ///< Requested feature missing in negotiated protocol version - http_tunnel_failed = RLM_SYNC_ERR_CLIENT_HTTP_TUNNEL_FAILED , ///< Failed to establish HTTP tunnel with configured proxy - auto_client_reset_failure = RLM_SYNC_ERR_CLIENT_AUTO_CLIENT_RESET_FAILURE , ///< A fatal error was encountered which prevents completion of a client reset - // clang-format on -}; - -const std::error_category& client_error_category() noexcept; - -std::error_code make_error_code(ClientError) noexcept; - -ProtocolError client_error_to_protocol_error(ClientError); -} // namespace realm::sync - -namespace std { - -template <> -struct is_error_code_enum { - static const bool value = true; -}; - -} // namespace std - -namespace realm::sync { - static constexpr milliseconds_type default_connect_timeout = 120000; // 2 minutes static constexpr milliseconds_type default_connection_linger_time = 30000; // 30 seconds static constexpr milliseconds_type default_ping_keepalive_period = 60000; // 1 minute diff --git a/src/realm/sync/config.cpp b/src/realm/sync/config.cpp index 34d8a51dade..b287312ca37 100644 --- a/src/realm/sync/config.cpp +++ b/src/realm/sync/config.cpp @@ -71,7 +71,7 @@ bool SyncError::is_client_reset_requested() const server_requests_action == sync::ProtocolErrorInfo::Action::ClientResetNoRecovery) { return true; } - if (status.get_std_error_code() == make_error_code(sync::Client::Error::auto_client_reset_failure)) { + if (status == ErrorCodes::AutoClientResetFailed) { return true; } return false; diff --git a/src/realm/sync/noinst/client_history_impl.cpp b/src/realm/sync/noinst/client_history_impl.cpp index 7a95be436dd..4a6fe0e9962 100644 --- a/src/realm/sync/noinst/client_history_impl.cpp +++ b/src/realm/sync/noinst/client_history_impl.cpp @@ -404,8 +404,9 @@ void ClientHistory::integrate_server_changesets( } } catch (const BadChangesetError& e) { - throw IntegrationException(ClientError::bad_changeset, - util::format("Failed to parse received changeset: %1", e.what())); + throw IntegrationException(ErrorCodes::BadChangeset, + util::format("Failed to parse received changeset: %1", e.what()), + ProtocolError::bad_changeset); } VersionID new_version{0, 0}; @@ -561,12 +562,14 @@ size_t ClientHistory::transform_and_apply_server_changesets(util::Span 0 && last_integrated_server_version < version_type(root.get_as_ref_or_tagged(s_progress_upload_server_version_iip).get_as_int())) { - throw IntegrationException(ClientError::bad_progress, - "last integrated server version of upload cursor cannot decrease"); + throw IntegrationException(ErrorCodes::SyncProtocolInvariantFailed, + "last integrated server version of upload cursor cannot decrease", + ProtocolError::bad_progress); } auto uploaded_bytes = std::uint_fast64_t(root.get_as_ref_or_tagged(s_progress_uploaded_bytes_iip).get_as_int()); diff --git a/src/realm/sync/noinst/client_history_impl.hpp b/src/realm/sync/noinst/client_history_impl.hpp index 16cac907fd1..2b293296408 100644 --- a/src/realm/sync/noinst/client_history_impl.hpp +++ b/src/realm/sync/noinst/client_history_impl.hpp @@ -67,21 +67,16 @@ constexpr int get_client_history_schema_version() noexcept return 12; } -class IntegrationException : public std::runtime_error { +class IntegrationException : public RuntimeError { public: - IntegrationException(ClientError code, const std::string& msg) - : std::runtime_error(msg) - , m_error(code) + IntegrationException(ErrorCodes::Error error, std::string message, + ProtocolError error_for_server = ProtocolError::other_session_error) + : RuntimeError(error, message) + , error_for_server(error_for_server) { } - ClientError code() const noexcept - { - return m_error; - } - -private: - ClientError m_error; + ProtocolError error_for_server = ProtocolError::other_session_error; }; class ClientHistory final : public _impl::History, public TransformHistory { diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp index 246dc3e6994..053d519dc3c 100644 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ b/src/realm/sync/noinst/client_impl_base.cpp @@ -1,4 +1,3 @@ -#include "realm/sync/protocol.hpp" #include #include @@ -471,8 +470,9 @@ void Connection::websocket_connected_handler(const std::string& protocol) else { logger.error("Missing protocol info from server"); // Throws } - close_due_to_client_side_error(ClientError::bad_protocol_from_server, std::nullopt, IsFatal{true}, - ConnectionTerminationReason::bad_headers_in_http_response); // Throws + close_due_to_client_side_error( + {ErrorCodes::SyncProtocolNegotiationFailed, "Failed to negotiate websocket protocol"}, IsFatal{true}, + ConnectionTerminationReason::bad_headers_in_http_response); // Throws } @@ -548,37 +548,31 @@ bool Connection::websocket_closed_handler(bool was_clean, Status status) } case WebSocketError::websocket_message_too_big: { constexpr bool try_again = true; - auto ec = make_error_code(ProtocolError::limits_exceeded); auto message = util::format("Sync websocket closed because the server received a message that was too large: %1", status.reason()); - SessionErrorInfo error_info(Status{ec, message}, try_again); + SessionErrorInfo error_info(Status(ErrorCodes::LimitExceeded, std::move(message)), try_again); error_info.server_requests_action = ProtocolErrorInfo::Action::ClientReset; involuntary_disconnect(std::move(error_info), ConnectionTerminationReason::websocket_protocol_violation); // Throws break; } case WebSocketError::websocket_tls_handshake_failed: { - error_code = ClientError::ssl_server_cert_rejected; - close_due_to_client_side_error(Status(ClientError::ssl_server_cert_rejected, status.reason()), - IsFatal{false}, + close_due_to_client_side_error(Status(ErrorCodes::TlsHandshakeFailed, status.reason()), IsFatal{false}, ConnectionTerminationReason::ssl_certificate_rejected); // Throws break; } case WebSocketError::websocket_client_too_old: { - error_code = ClientError::client_too_old_for_server; close_due_to_client_side_error(std::move(status), IsFatal{true}, ConnectionTerminationReason::http_response_says_fatal_error); // Throws break; } case WebSocketError::websocket_client_too_new: { - error_code = ClientError::client_too_new_for_server; close_due_to_client_side_error(std::move(status), IsFatal{true}, ConnectionTerminationReason::http_response_says_fatal_error); // Throws break; } case WebSocketError::websocket_protocol_mismatch: { - error_code = ClientError::protocol_mismatch; close_due_to_client_side_error(std::move(status), IsFatal{true}, ConnectionTerminationReason::http_response_says_fatal_error); // Throws break; @@ -789,7 +783,7 @@ void Connection::handle_connect_wait(Status status) REALM_ASSERT_EX(m_state == ConnectionState::connecting, m_state); logger.info("Connect timeout"); // Throws constexpr bool try_again = true; - involuntary_disconnect(SessionErrorInfo{Status{ClientError::connect_timeout, status.reason()}, try_again}, + involuntary_disconnect(SessionErrorInfo{Status{ErrorCodes::SyncConnectFailed, status.reason()}, try_again}, ConnectionTerminationReason::sync_connect_timeout); // Throws } @@ -928,8 +922,8 @@ void Connection::handle_pong_timeout() { REALM_ASSERT(m_waiting_for_pong); logger.debug("Timeout on reception of PONG message"); // Throws - close_due_to_client_side_error(ClientError::pong_timeout, std::nullopt, IsFatal{false}, - ConnectionTerminationReason::pong_timeout); + close_due_to_transient_error({ErrorCodes::ConnectionClosed, "Timed out waiting for PONG response from server"}, + ConnectionTerminationReason::pong_timeout); } @@ -1101,9 +1095,9 @@ void Connection::read_or_write_error(std::error_code ec, std::string_view msg) } -void Connection::close_due_to_protocol_error(std::error_code ec, std::optional msg) +void Connection::close_due_to_protocol_error(Status status) { - close_due_to_client_side_error(ec, msg, IsFatal{true}, + close_due_to_client_side_error(std::move(status), IsFatal{true}, ConnectionTerminationReason::sync_protocol_violation); // Throws } @@ -1128,6 +1122,15 @@ void Connection::close_due_to_client_side_error(Status status, IsFatal is_fatal, involuntary_disconnect(SessionErrorInfo{std::move(status), try_again}, reason); // Throw } +void Connection::close_due_to_transient_error(Status status, ConnectionTerminationReason reason) +{ + logger.info("Connection closed due to error"); // Throws + SessionErrorInfo error_info{std::move(status), true}; + error_info.server_requests_action = ProtocolErrorInfo::Action::Transient; + + involuntary_disconnect(std::move(error_info), reason); // Throw +} + // Close connection due to error discovered on the server-side, and then // reported to the client by way of a connection-level ERROR message. void Connection::close_due_to_server_side_error(ProtocolError error_code, const ProtocolErrorInfo& info) @@ -1135,10 +1138,10 @@ void Connection::close_due_to_server_side_error(ProtocolError error_code, const logger.info("Connection closed due to error reported by server: %1 (%2)", info.message, int(error_code)); // Throws - std::error_code ec = make_error_code(error_code); const auto reason = info.try_again ? ConnectionTerminationReason::server_said_try_again_later : ConnectionTerminationReason::server_said_do_not_reconnect; - involuntary_disconnect(SessionErrorInfo{info, Status(ec, info.message)}, reason); // Throws + involuntary_disconnect(SessionErrorInfo{info, protocol_error_to_status(error_code, info.message)}, + reason); // Throws } @@ -1201,16 +1204,16 @@ void Connection::receive_pong(milliseconds_type timestamp) bool legal_at_this_time = (m_waiting_for_pong && !m_send_ping); if (REALM_UNLIKELY(!legal_at_this_time)) { - logger.error("Illegal message at this time"); - std::error_code ec = ClientError::bad_message_order; - close_due_to_protocol_error(ec); // Throws + close_due_to_protocol_error( + {ErrorCodes::SyncProtocolInvariantFailed, "Received PONG message when it was not valid"}); // Throws return; } if (REALM_UNLIKELY(timestamp != m_last_ping_sent_at)) { - logger.error("Bad timestamp in PONG message"); - std::error_code ec = ClientError::bad_timestamp; - close_due_to_protocol_error(ec); // Throws + close_due_to_protocol_error( + {ErrorCodes::SyncProtocolInvariantFailed, + util::format("Received PONG message with an invalid timestamp (expected %1, received %2", + m_last_ping_sent_at, timestamp)}); // Throws return; } @@ -1250,7 +1253,10 @@ Session* Connection::find_and_validate_session(session_ident_type session_ident, // Check the history to see if the message received was for a previous session if (auto it = m_session_history.find(session_ident); it == m_session_history.end()) { logger.error("Bad session identifier in %1 message, session_ident = %2", message, session_ident); - close_due_to_protocol_error(ClientError::bad_session_ident); // Throws + close_due_to_protocol_error( + {ErrorCodes::SyncProtocolInvariantFailed, + util::format("Received message %1 for session iden %2 when that session never existed", message, + session_ident)}); } else { logger.error("Received %1 message for closed session, session_ident = %2", message, @@ -1267,9 +1273,8 @@ void Connection::receive_error_message(const ProtocolErrorInfo& info, session_id if (REALM_UNLIKELY(!sess)) { return; } - std::error_code ec = sess->receive_error_message(info); // Throws - if (ec) { - close_due_to_protocol_error(ec); // Throws + if (auto status = sess->receive_error_message(info); !status.is_ok()) { + close_due_to_protocol_error(std::move(status)); // Throws return; } @@ -1290,12 +1295,16 @@ void Connection::receive_error_message(const ProtocolErrorInfo& info, session_id close_due_to_server_side_error(error_code, info); // Throws return; } - logger.error("Not a connection-level error code"); // Throws + close_due_to_protocol_error( + {ErrorCodes::SyncProtocolInvariantFailed, + util::format("Received ERROR message with a non-connection-level error code %1 without a session ident", + info.raw_error_code)}); } else { - logger.error("Unknown error code"); // Throws + close_due_to_protocol_error( + {ErrorCodes::SyncProtocolInvariantFailed, + util::format("Received ERROR message with unknown error code %1", info.raw_error_code)}); } - close_due_to_protocol_error(ClientError::bad_error_code); // Throws } @@ -1303,13 +1312,13 @@ void Connection::receive_query_error_message(int raw_error_code, std::string_vie session_ident_type session_ident) { if (session_ident == 0) { - logger.error("Received query error message for session ident 0."); // throws; - return close_due_to_protocol_error(ClientError::bad_session_ident); + return close_due_to_protocol_error( + {ErrorCodes::SyncProtocolInvariantFailed, "Received query error message for session ident 0"}); } if (!is_flx_sync_connection()) { - logger.error("Received query error message on a non-FLX sync connection"); - return close_due_to_protocol_error(ClientError::bad_protocol_from_server); + return close_due_to_protocol_error({ErrorCodes::SyncProtocolInvariantFailed, + "Received a FLX query error message on a non-FLX sync connection"}); } Session* sess = find_and_validate_session(session_ident, "QUERY_ERROR"); @@ -1317,8 +1326,8 @@ void Connection::receive_query_error_message(int raw_error_code, std::string_vie return; } - if (auto ec = sess->receive_query_error_message(raw_error_code, message, query_version)) { - close_due_to_protocol_error(ec); + if (auto status = sess->receive_query_error_message(raw_error_code, message, query_version); !status.is_ok()) { + close_due_to_protocol_error(std::move(status)); } } @@ -1330,9 +1339,8 @@ void Connection::receive_ident_message(session_ident_type session_ident, SaltedF return; } - std::error_code ec = sess->receive_ident_message(client_file_ident); // Throws - if (ec) - close_due_to_protocol_error(ec); // Throws + if (auto status = sess->receive_ident_message(client_file_ident); !status.is_ok()) + close_due_to_protocol_error(std::move(status)); // Throws } void Connection::receive_download_message(session_ident_type session_ident, const SyncProgress& progress, @@ -1345,8 +1353,11 @@ void Connection::receive_download_message(session_ident_type session_ident, cons return; } - sess->receive_download_message(progress, downloadable_bytes, batch_state, query_version, - received_changesets); // Throws + if (auto status = sess->receive_download_message(progress, downloadable_bytes, batch_state, query_version, + received_changesets); + !status.is_ok()) { + close_due_to_protocol_error(std::move(status)); + } } void Connection::receive_mark_message(session_ident_type session_ident, request_ident_type request_ident) @@ -1356,9 +1367,8 @@ void Connection::receive_mark_message(session_ident_type session_ident, request_ return; } - std::error_code ec = sess->receive_mark_message(request_ident); // Throws - if (ec) - close_due_to_protocol_error(ec); // Throws + if (auto status = sess->receive_mark_message(request_ident); !status.is_ok()) + close_due_to_protocol_error(std::move(status)); // Throws } @@ -1369,9 +1379,8 @@ void Connection::receive_unbound_message(session_ident_type session_ident) return; } - std::error_code ec = sess->receive_unbound_message(); // Throws - if (ec) { - close_due_to_protocol_error(ec); // Throws + if (auto status = sess->receive_unbound_message(); !status.is_ok()) { + close_due_to_protocol_error(std::move(status)); // Throws return; } @@ -1389,38 +1398,34 @@ void Connection::receive_test_command_response(session_ident_type session_ident, return; } - if (auto ec = sess->receive_test_command_response(request_ident, body)) { - close_due_to_protocol_error(ec); + if (auto status = sess->receive_test_command_response(request_ident, body); !status.is_ok()) { + close_due_to_protocol_error(std::move(status)); } } -void Connection::handle_protocol_error(ClientProtocol::Error error) +void Connection::handle_protocol_error(ClientProtocol::Error error, std::string msg) { switch (error) { case ClientProtocol::Error::unknown_message: - close_due_to_protocol_error(ClientError::unknown_message); // Throws - break; + [[fallthrough]]; case ClientProtocol::Error::bad_syntax: - close_due_to_protocol_error(ClientError::bad_syntax); // Throws + [[fallthrough]]; + case ClientProtocol::Error::bad_changeset_header_syntax: + [[fallthrough]]; + case ClientProtocol::Error::bad_changeset_size: + [[fallthrough]]; + case ClientProtocol::Error::bad_server_version: + close_due_to_protocol_error({ErrorCodes::SyncProtocolInvariantFailed, std::move(msg)}); // Throws break; case ClientProtocol::Error::limits_exceeded: - close_due_to_protocol_error(ClientError::limits_exceeded); // Throws + close_due_to_protocol_error({ErrorCodes::LimitExceeded, std::move(msg)}); // Throws break; case ClientProtocol::Error::bad_decompression: - close_due_to_protocol_error(ClientError::bad_compression); // Throws - break; - case ClientProtocol::Error::bad_changeset_header_syntax: - close_due_to_protocol_error(ClientError::bad_changeset_header_syntax); // Throws - break; - case ClientProtocol::Error::bad_changeset_size: - close_due_to_protocol_error(ClientError::bad_changeset_size); // Throws - break; - case ClientProtocol::Error::bad_server_version: - close_due_to_protocol_error(ClientError::bad_server_version); // Throws + close_due_to_protocol_error({ErrorCodes::RuntimeError, std::move(msg)}); // Throws break; case ClientProtocol::Error::bad_error_code: - close_due_to_protocol_error(ClientError::bad_error_code); // Throws + close_due_to_protocol_error({ErrorCodes::UnknownError, std::move(msg)}); // Throws break; } } @@ -1506,8 +1511,9 @@ void Session::integrate_changesets(ClientReplication& repl, const SyncProgress& auto& history = repl.get_history(); if (received_changesets.empty()) { if (download_batch_state == DownloadBatchState::MoreToCome) { - throw IntegrationException(ClientError::bad_progress, - "received empty download message that was not the last in batch"); + throw IntegrationException(ErrorCodes::SyncProtocolInvariantFailed, + "received empty download message that was not the last in batch", + ProtocolError::bad_progress); } history.set_sync_progress(progress, &downloadable_bytes, version_info); // Throws return; @@ -1535,10 +1541,11 @@ void Session::integrate_changesets(ClientReplication& repl, const SyncProgress& pending_error.compensating_write_rejected_client_version, pending_error.compensating_write_server_version, pending_error.message); try { - ProtocolError error_code = ProtocolError(pending_error.raw_error_code); on_connection_state_changed( m_conn.get_state(), - SessionErrorInfo{pending_error, Status{make_error_code(error_code), pending_error.message}}); + SessionErrorInfo{pending_error, + protocol_error_to_status(static_cast(pending_error.raw_error_code), + pending_error.message)}); } catch (...) { logger.error("Exception thrown while reporting compensating write: %1", exception_to_status()); @@ -1557,11 +1564,8 @@ void Session::on_integration_failure(const IntegrationException& error) m_error_to_send = true; constexpr bool try_again = true; - std::error_code error_code = error.code(); - auto msg = error_code.message() + ": " + error.what(); // Surface the error to the user otherwise is lost. - on_connection_state_changed(m_conn.get_state(), - SessionErrorInfo{Status{error.code(), std::move(msg)}, try_again}); + on_connection_state_changed(m_conn.get_state(), SessionErrorInfo{error.to_status(), try_again}); // Since the deactivation process has not been initiated, the UNBIND // message cannot have been sent unless an ERROR message was received. @@ -2148,15 +2152,14 @@ void Session::send_json_error_message() ClientProtocol& protocol = m_conn.get_client_protocol(); OutputBuffer& out = m_conn.get_output_buffer(); session_ident_type session_ident = get_ident(); - auto client_error = m_client_error->code(); - auto protocol_error = client_error_to_protocol_error(client_error); - auto message = m_client_error->what(); + auto protocol_error = m_client_error->error_for_server; + auto message = util::format("%1", m_client_error->to_status()); logger.info("Sending: ERROR \"%1\" (error_code=%2, session_ident=%3)", message, static_cast(protocol_error), session_ident); // Throws nlohmann::json error_body_json; - error_body_json["message"] = message; + error_body_json["message"] = std::move(message); protocol.make_json_error_message(out, session_ident, static_cast(protocol_error), error_body_json.dump()); // Throws m_conn.initiate_write_message(out, this); // Throws @@ -2190,7 +2193,7 @@ void Session::send_test_command_message() } -std::error_code Session::receive_ident_message(SaltedFileIdent client_file_ident) +Status Session::receive_ident_message(SaltedFileIdent client_file_ident) { logger.debug("Received: IDENT(client_file_ident=%1, client_file_ident_salt=%2)", client_file_ident.ident, client_file_ident.salt); // Throws @@ -2199,21 +2202,18 @@ std::error_code Session::receive_ident_message(SaltedFileIdent client_file_ident // because in that case, the associated Realm and SessionWrapper must // not be accessed any longer. if (m_state != Active) - return std::error_code{}; // Success + return Status::OK(); // Success 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)) { - logger.error("Illegal message at this time"); - return ClientError::bad_message_order; + return {ErrorCodes::SyncProtocolInvariantFailed, "Received IDENT message when it was not legal"}; } if (REALM_UNLIKELY(client_file_ident.ident < 1)) { - logger.error("Bad client file identifier in IDENT message"); - return ClientError::bad_client_file_ident; + return {ErrorCodes::SyncProtocolInvariantFailed, "Bad client file identifier in IDENT message"}; } if (REALM_UNLIKELY(client_file_ident.salt == 0)) { - logger.error("Bad client file identifier salt in IDENT message"); - return ClientError::bad_client_file_ident_salt; + return {ErrorCodes::SyncProtocolInvariantFailed, "Bad client file identifier salt in IDENT message"}; } m_client_file_ident = client_file_ident; @@ -2221,7 +2221,7 @@ std::error_code Session::receive_ident_message(SaltedFileIdent client_file_ident if (REALM_UNLIKELY(get_client().is_dry_run())) { // Ready to send the IDENT message ensure_enlisted_to_send(); // Throws - return std::error_code{}; // Success + return Status::OK(); // Success } // access before the client reset (if applicable) because @@ -2297,9 +2297,9 @@ std::error_code Session::receive_ident_message(SaltedFileIdent client_file_ident catch (const std::exception& e) { auto err_msg = util::format("A fatal error occurred during client reset: '%1'", e.what()); logger.error(err_msg.c_str()); - SessionErrorInfo err_info(Status{make_error_code(ClientError::auto_client_reset_failure), err_msg}, false); + SessionErrorInfo err_info(Status{ErrorCodes::AutoClientResetFailed, err_msg}, false); suspend(err_info); - return {}; + return Status::OK(); } if (!did_client_reset) { repl.get_history().set_client_file_ident(client_file_ident, m_fix_up_object_ids); // Throws @@ -2310,18 +2310,18 @@ std::error_code Session::receive_ident_message(SaltedFileIdent client_file_ident // Ready to send the IDENT message ensure_enlisted_to_send(); // Throws - return std::error_code{}; // Success + return Status::OK(); // Success } -void Session::receive_download_message(const SyncProgress& progress, std::uint_fast64_t downloadable_bytes, - DownloadBatchState batch_state, int64_t query_version, - const ReceivedChangesets& received_changesets) +Status Session::receive_download_message(const SyncProgress& progress, std::uint_fast64_t downloadable_bytes, + DownloadBatchState batch_state, int64_t query_version, + const ReceivedChangesets& received_changesets) { // Ignore the message if the deactivation process has been initiated, // because in that case, the associated Realm and SessionWrapper must // not be accessed any longer. if (m_state != Active) - return; + return Status::OK(); if (is_steady_state_download_message(batch_state, query_version)) { batch_state = DownloadBatchState::SteadyState; @@ -2334,40 +2334,36 @@ void Session::receive_download_message(const SyncProgress& progress, std::uint_f progress.download.server_version, progress.download.last_integrated_client_version, progress.latest_server_version.version, progress.latest_server_version.salt, progress.upload.client_version, progress.upload.last_integrated_server_version, downloadable_bytes, - batch_state != DownloadBatchState::MoreToCome, query_version, received_changesets.size()); // Throws + batch_state != DownloadBatchState::MoreToCome, query_version, + received_changesets.size()); // Throws // Ignore download messages when the client detects an error. This is to prevent transforming the same bad // changeset over and over again. if (m_client_error) { logger.debug("Ignoring download message because the client detected an integration error"); - return; + return Status::OK(); } bool legal_at_this_time = (m_ident_message_sent && !m_error_message_received && !m_unbound_message_received); if (REALM_UNLIKELY(!legal_at_this_time)) { - logger.error("Illegal message at this time"); - m_conn.close_due_to_protocol_error(ClientError::bad_message_order); - return; + return {ErrorCodes::SyncProtocolInvariantFailed, "Received DOWNLOAD message when it was not legal"}; } - int error_code = 0; - if (REALM_UNLIKELY(!check_received_sync_progress(progress, error_code))) { - logger.error("Bad sync progress received (%1)", error_code); - m_conn.close_due_to_protocol_error(ClientError::bad_progress); - return; + if (auto status = check_received_sync_progress(progress); REALM_UNLIKELY(!status.is_ok())) { + logger.error("Bad sync progress received (%1)", status); + return status; } version_type server_version = m_progress.download.server_version; version_type last_integrated_client_version = m_progress.download.last_integrated_client_version; for (const Transformer::RemoteChangeset& changeset : received_changesets) { - // Check that per-changeset server version is strictly increasing, except in FLX sync where the server version - // must be increasing, but can stay the same during bootstraps. + // Check that per-changeset server version is strictly increasing, except in FLX sync where the server + // version must be increasing, but can stay the same during bootstraps. bool good_server_version = m_is_flx_sync_session ? (changeset.remote_version >= server_version) : (changeset.remote_version > server_version); if (!good_server_version) { - logger.error("Bad server version in changeset header (DOWNLOAD) (%1, %2, %3)", changeset.remote_version, - server_version, progress.download.server_version); - m_conn.close_due_to_protocol_error(ClientError::bad_server_version); - return; + return {ErrorCodes::SyncProtocolInvariantFailed, + util::format("Bad server version in changeset header (DOWNLOAD) (%1, %2, %3)", + changeset.remote_version, server_version, progress.download.server_version)}; } server_version = changeset.remote_version; // Check that per-changeset last integrated client version is "weakly" @@ -2376,12 +2372,11 @@ void Session::receive_download_message(const SyncProgress& progress, std::uint_f (changeset.last_integrated_local_version >= last_integrated_client_version && changeset.last_integrated_local_version <= progress.download.last_integrated_client_version); if (!good_client_version) { - logger.error("Bad last integrated client version in changeset header (DOWNLOAD) " - "(%1, %2, %3)", - changeset.last_integrated_local_version, last_integrated_client_version, - progress.download.last_integrated_client_version); - m_conn.close_due_to_protocol_error(ClientError::bad_client_version); - return; + return {ErrorCodes::SyncProtocolInvariantFailed, + util::format("Bad last integrated client version in changeset header (DOWNLOAD) " + "(%1, %2, %3)", + changeset.last_integrated_local_version, last_integrated_client_version, + progress.download.last_integrated_client_version)}; } last_integrated_client_version = changeset.last_integrated_local_version; // Server shouldn't send our own changes, and zero is not a valid client @@ -2389,22 +2384,22 @@ void Session::receive_download_message(const SyncProgress& progress, std::uint_f bool good_file_ident = (changeset.origin_file_ident > 0 && changeset.origin_file_ident != m_client_file_ident.ident); if (!good_file_ident) { - logger.error("Bad origin file identifier"); - m_conn.close_due_to_protocol_error(ClientError::bad_origin_file_ident); - return; + return {ErrorCodes::SyncProtocolInvariantFailed, + util::format("Bad origin file identifier in changeset header (DOWNLOAD)", + changeset.origin_file_ident)}; } } auto hook_action = call_debug_hook(SyncClientHookEvent::DownloadMessageReceived, progress, query_version, batch_state, received_changesets.size()); if (hook_action == SyncClientHookAction::EarlyReturn) { - return; + return Status::OK(); } REALM_ASSERT_EX(hook_action == SyncClientHookAction::NoAction, hook_action); if (process_flx_bootstrap_message(progress, batch_state, query_version, received_changesets)) { clear_resumption_delay_state(); - return; + return Status::OK(); } initiate_integrate_changesets(downloadable_bytes, batch_state, progress, received_changesets); // Throws @@ -2412,16 +2407,17 @@ void Session::receive_download_message(const SyncProgress& progress, std::uint_f hook_action = call_debug_hook(SyncClientHookEvent::DownloadMessageIntegrated, progress, query_version, batch_state, received_changesets.size()); if (hook_action == SyncClientHookAction::EarlyReturn) { - return; + return Status::OK(); } REALM_ASSERT_EX(hook_action == SyncClientHookAction::NoAction, hook_action); // When we receive a DOWNLOAD message successfully, we can clear the backoff timer value used to reconnect // after a retryable session error. clear_resumption_delay_state(); + return Status::OK(); } -std::error_code Session::receive_mark_message(request_ident_type request_ident) +Status Session::receive_mark_message(request_ident_type request_ident) { logger.debug("Received: MARK(request_ident=%1)", request_ident); // Throws @@ -2429,38 +2425,38 @@ std::error_code Session::receive_mark_message(request_ident_type request_ident) // because in that case, the associated Realm and SessionWrapper must // not be accessed any longer. if (m_state != Active) - return std::error_code{}; // Success + return Status::OK(); // Success bool legal_at_this_time = (m_ident_message_sent && !m_error_message_received && !m_unbound_message_received); if (REALM_UNLIKELY(!legal_at_this_time)) { - logger.error("Illegal message at this time"); - return ClientError::bad_message_order; + return {ErrorCodes::SyncProtocolInvariantFailed, "Received MARK message when it was not legal"}; } bool good_request_ident = (request_ident <= m_last_download_mark_sent && request_ident > m_last_download_mark_received); if (REALM_UNLIKELY(!good_request_ident)) { - logger.error("Bad request identifier in MARK message"); - return ClientError::bad_request_ident; + return {ErrorCodes::SyncProtocolInvariantFailed, + util::format( + "Received MARK message with invalid request identifer (last mark sent: %1 last mark received: %2", + m_last_download_mark_sent, m_last_download_mark_received)}; } m_server_version_at_last_download_mark = m_progress.download.server_version; m_last_download_mark_received = request_ident; check_for_download_completion(); // Throws - return std::error_code{}; // Success + return Status::OK(); // Success } // The caller (Connection) must discard the session if the session has become // deactivated upon return. -std::error_code Session::receive_unbound_message() +Status Session::receive_unbound_message() { logger.debug("Received: UNBOUND"); bool legal_at_this_time = (m_unbind_message_sent && !m_error_message_received && !m_unbound_message_received); if (REALM_UNLIKELY(!legal_at_this_time)) { - logger.error("Illegal message at this time"); - return ClientError::bad_message_order; + return {ErrorCodes::SyncProtocolInvariantFailed, "Received UNBOUND message when it was not legal"}; } // The fact that the UNBIND message has been sent, but an ERROR message has @@ -2479,11 +2475,11 @@ std::error_code Session::receive_unbound_message() // Life cycle state is now Deactivated } - return std::error_code{}; // Success + return Status::OK(); // Success } -std::error_code Session::receive_query_error_message(int error_code, std::string_view message, int64_t query_version) +Status Session::receive_query_error_message(int error_code, std::string_view message, int64_t query_version) { logger.info("Received QUERY_ERROR \"%1\" (error_code=%2, query_version=%3)", message, error_code, query_version); // Ignore the message if the deactivation process has been initiated, @@ -2492,31 +2488,31 @@ std::error_code Session::receive_query_error_message(int error_code, std::string if (m_state == Active) { on_flx_sync_error(query_version, std::string_view(message.data(), message.size())); // throws } - return {}; + return Status::OK(); } // The caller (Connection) must discard the session if the session has become // deactivated upon return. -std::error_code Session::receive_error_message(const ProtocolErrorInfo& info) +Status Session::receive_error_message(const ProtocolErrorInfo& info) { logger.info("Received: ERROR \"%1\" (error_code=%2, try_again=%3, error_action=%4)", info.message, info.raw_error_code, info.try_again, info.server_requests_action); // Throws bool legal_at_this_time = (m_bind_message_sent && !m_error_message_received && !m_unbound_message_received); if (REALM_UNLIKELY(!legal_at_this_time)) { - logger.error("Illegal message at this time"); - return ClientError::bad_message_order; + return {ErrorCodes::SyncProtocolInvariantFailed, "Received ERROR message when it was not legal"}; } bool known_error_code = bool(get_protocol_error_message(info.raw_error_code)); if (REALM_UNLIKELY(!known_error_code)) { - logger.error("Unknown error code"); // Throws - return ClientError::bad_error_code; + return {ErrorCodes::SyncProtocolInvariantFailed, + util::format("Received ERROR message with unknown error code %1", info.raw_error_code)}; } ProtocolError error_code = ProtocolError(info.raw_error_code); if (REALM_UNLIKELY(!is_session_level_error(error_code))) { - logger.error("Not a session level error code"); // Throws - return ClientError::bad_error_code; + return {ErrorCodes::SyncProtocolInvariantFailed, + util::format("Received ERROR message for session with non-session-level error code %1", + info.raw_error_code)}; } // Can't process debug hook actions once the Session is undergoing deactivation, since @@ -2524,7 +2520,7 @@ std::error_code Session::receive_error_message(const ProtocolErrorInfo& info) if (m_state == Active) { auto debug_action = call_debug_hook(SyncClientHookEvent::ErrorMessageReceived, info); if (debug_action == SyncClientHookAction::EarlyReturn) { - return {}; + return Status::OK(); } } @@ -2536,12 +2532,12 @@ std::error_code Session::receive_error_message(const ProtocolErrorInfo& info) if (m_state == Active) { m_pending_compensating_write_errors.push_back(info); } - return {}; + return Status::OK(); } m_error_message_received = true; - suspend(SessionErrorInfo{info, Status{make_error_code(error_code), info.message}}); - return {}; + suspend(SessionErrorInfo{info, protocol_error_to_status(error_code, info.message)}); + return Status::OK(); } void Session::suspend(const SessionErrorInfo& info) @@ -2581,7 +2577,7 @@ void Session::suspend(const SessionErrorInfo& info) ensure_enlisted_to_send(); // Throws } -std::error_code Session::receive_test_command_response(request_ident_type ident, std::string_view body) +Status Session::receive_test_command_response(request_ident_type ident, std::string_view body) { logger.info("Received: TEST_COMMAND \"%1\" (session_ident=%2, request_ident=%3)", body, m_ident, ident); auto it = std::find_if(m_pending_test_commands.begin(), m_pending_test_commands.end(), @@ -2589,14 +2585,14 @@ std::error_code Session::receive_test_command_response(request_ident_type ident, return command.id == ident; }); if (it == m_pending_test_commands.end()) { - logger.error("No matching pending test command for id %1", ident); - return ClientError::bad_request_ident; + return {ErrorCodes::SyncProtocolInvariantFailed, + util::format("Received TEST_COMMAND for request ident %1 which does not exist", ident)}; } it->promise.emplace_value(std::string{body}); m_pending_test_commands.erase(it); - return {}; + return Status::OK(); } void Session::begin_resumption_delay(const ProtocolErrorInfo& error_info) @@ -2607,8 +2603,8 @@ void Session::begin_resumption_delay(const ProtocolErrorInfo& error_info) error_info.resumption_delay_interval); auto try_again_interval = m_try_again_delay_info.delay_interval(); if (ProtocolError(error_info.raw_error_code) == ProtocolError::session_closed) { - // FIXME With compensating writes the server sends this error after completing a bootstrap. Doing the normal - // backoff behavior would result in waiting up to 5 minutes in between each query change which is + // FIXME With compensating writes the server sends this error after completing a bootstrap. Doing the + // normal backoff behavior would result in waiting up to 5 minutes in between each query change which is // not acceptable latency. So for this error code alone, we hard-code a 1 second retry interval. try_again_interval = std::chrono::milliseconds{1000}; } @@ -2632,53 +2628,53 @@ void Session::clear_resumption_delay_state() } } -bool ClientImpl::Session::check_received_sync_progress(const SyncProgress& progress, int& error_code) noexcept +Status ClientImpl::Session::check_received_sync_progress(const SyncProgress& progress) noexcept { const SyncProgress& a = m_progress; const SyncProgress& b = progress; - // Latest server version must be weakly increasing throughout a session. + std::string message; if (b.latest_server_version.version < a.latest_server_version.version) { - error_code = 1; - return false; + message = util::format("Latest server version in download messages must be weakly increasing throughout a " + "session (current: %1, received: %2)", + a.latest_server_version.version, b.latest_server_version.version); } - // Last integrated client version on server must be weakly increasing - // throughout a session. if (b.upload.client_version < a.upload.client_version) { - error_code = 2; - return false; + message = util::format("Last integrated client version in download messages must be weakly increasing " + "throughout a session (current: %1, received: %2)", + a.upload.client_version, b.upload.client_version); } - // Last integrated client version on server cannot be greater than the - // latest client version in existence. if (b.upload.client_version > m_last_version_available) { - error_code = 3; - return false; + message = util::format("Last integrated client version on server cannot be greater than the latest client " + "version in existence (current: %1, received: %2)", + m_last_version_available, b.upload.client_version); } - // Download cursor must be weakly increasing throughout a session if (b.download.server_version < a.download.server_version) { - error_code = 4; - return false; + message = + util::format("Download cursor must be weakly increasing throughout a session (current: %1, received: %2)", + a.download.server_version, b.download.server_version); } - // Download cursor cannot be greater than the latest server version in - // existence. if (b.download.server_version > b.latest_server_version.version) { - error_code = 5; - return false; + message = util::format( + "Download cursor cannot be greater than the latest server version in existence (cursor: %1, latest: %2)", + b.download.server_version, b.latest_server_version.version); } - // The last integrated client version on the server at the position in the - // server's history of the download cursor must be weakly increasing - // throughout a session. if (b.download.last_integrated_client_version < a.download.last_integrated_client_version) { - error_code = 6; - return false; + message = util::format( + "Last integrated client version on the server at the position in the server's history of the download " + "cursor must be weakly increasing throughout a session (current: %1, received: %2)", + a.download.last_integrated_client_version, b.download.last_integrated_client_version); } - // The last integrated client version on the server at the position in the - // server's history of the download cursor cannot be greater than the latest - // client version integrated on the server. if (b.download.last_integrated_client_version > b.upload.client_version) { - error_code = 7; - return false; + message = util::format("Last integrated client version on the server in the position at the server's history " + "of the download cursor cannot be greater than the latest client version integrated " + "on the server (download: %1, upload: %2)", + b.download.last_integrated_client_version, b.upload.client_version); } - return true; + + if (message.empty()) { + return Status::OK(); + } + return {ErrorCodes::SyncProtocolInvariantFailed, std::move(message)}; } diff --git a/src/realm/sync/noinst/client_impl_base.hpp b/src/realm/sync/noinst/client_impl_base.hpp index 390508d16c1..bb5e4031685 100644 --- a/src/realm/sync/noinst/client_impl_base.hpp +++ b/src/realm/sync/noinst/client_impl_base.hpp @@ -564,8 +564,9 @@ class ClientImpl::Connection { void initiate_disconnect_wait(); void handle_disconnect_wait(Status status); void read_or_write_error(std::error_code ec, std::string_view msg); - void close_due_to_protocol_error(std::error_code, std::optional msg = std::nullopt); + void close_due_to_protocol_error(Status status); + void close_due_to_transient_error(Status status, ConnectionTerminationReason reason); void close_due_to_client_side_error(Status status, IsFatal is_fatal, ConnectionTerminationReason reason); void close_due_to_client_side_error(std::error_code, std::optional msg, IsFatal is_fatal, ConnectionTerminationReason reason); @@ -584,7 +585,7 @@ class ClientImpl::Connection { void receive_mark_message(session_ident_type, request_ident_type); void receive_unbound_message(session_ident_type); void receive_test_command_response(session_ident_type, request_ident_type, std::string_view body); - void handle_protocol_error(ClientProtocol::Error); + void handle_protocol_error(ClientProtocol::Error, std::string message); // These are only called from Session class. void enlist_to_send(Session*); @@ -1211,21 +1212,21 @@ class ClientImpl::Session { void send_query_change_message(); void send_json_error_message(); void send_test_command_message(); - std::error_code receive_ident_message(SaltedFileIdent); - void receive_download_message(const SyncProgress&, std::uint_fast64_t downloadable_bytes, - DownloadBatchState last_in_batch, int64_t query_version, const ReceivedChangesets&); - std::error_code receive_mark_message(request_ident_type); - std::error_code receive_unbound_message(); - std::error_code receive_error_message(const ProtocolErrorInfo& info); - std::error_code receive_query_error_message(int error_code, std::string_view message, int64_t query_version); - std::error_code receive_test_command_response(request_ident_type, std::string_view body); + Status receive_ident_message(SaltedFileIdent); + Status receive_download_message(const SyncProgress&, std::uint_fast64_t downloadable_bytes, + DownloadBatchState last_in_batch, int64_t query_version, + const ReceivedChangesets&); + Status receive_mark_message(request_ident_type); + Status receive_unbound_message(); + Status receive_error_message(const ProtocolErrorInfo& info); + Status receive_query_error_message(int error_code, std::string_view message, int64_t query_version); + Status receive_test_command_response(request_ident_type, std::string_view body); void initiate_rebind(); void reset_protocol_state() noexcept; void ensure_enlisted_to_send(); void enlist_to_send(); - bool check_received_sync_progress(const SyncProgress&) noexcept; - bool check_received_sync_progress(const SyncProgress&, int&) noexcept; + Status check_received_sync_progress(const SyncProgress&) noexcept; void check_for_upload_completion(); void check_for_download_completion(); @@ -1312,7 +1313,10 @@ inline void ClientImpl::Connection::voluntary_disconnect() { m_reconnect_info.update(ConnectionTerminationReason::closed_voluntarily, std::nullopt); constexpr bool try_again = true; - disconnect(SessionErrorInfo{Status{ClientError::connection_closed, "Connection closed"}, try_again}); // Throws + SessionErrorInfo error_info{Status{ErrorCodes::ConnectionClosed, "Connection closed"}, try_again}; + error_info.server_requests_action = ProtocolErrorInfo::Action::Transient; + + disconnect(std::move(error_info)); // Throws } inline void ClientImpl::Connection::involuntary_disconnect(const SessionErrorInfo& info, @@ -1632,12 +1636,6 @@ inline void ClientImpl::Session::enlist_to_send() m_conn.enlist_to_send(this); // Throws } -inline bool ClientImpl::Session::check_received_sync_progress(const SyncProgress& progress) noexcept -{ - int error_code = 0; // Dummy - return check_received_sync_progress(progress, error_code); -} - } // namespace sync } // namespace realm diff --git a/src/realm/sync/noinst/protocol_codec.hpp b/src/realm/sync/noinst/protocol_codec.hpp index 35d68d5c1b9..df4ffa60b1b 100644 --- a/src/realm/sync/noinst/protocol_codec.hpp +++ b/src/realm/sync/noinst/protocol_codec.hpp @@ -224,8 +224,8 @@ class ClientProtocol { { util::Logger& logger = connection.logger; auto report_error = [&](Error err, const auto fmt, auto&&... args) { - logger.error(fmt, std::forward(args)...); - connection.handle_protocol_error(err); + auto msg = util::format(fmt, std::forward(args)...); + connection.handle_protocol_error(err, std::move(msg)); }; HeaderLineParser msg(msg_data); @@ -389,8 +389,8 @@ class ClientProtocol { { util::Logger& logger = connection.logger; auto report_error = [&](Error err, const auto fmt, auto&&... args) { - logger.error(fmt, std::forward(args)...); - connection.handle_protocol_error(err); + auto msg = util::format(fmt, std::forward(args)...); + connection.handle_protocol_error(err, std::move(msg)); }; auto msg_with_header = msg.remaining(); @@ -601,8 +601,8 @@ class ServerProtocol { connection.receive_ping(timestamp, rtt); } catch (const ProtocolCodecException& e) { - connection.logger.error("Bad syntax in ping message: %1", e.what()); - connection.handle_protocol_error(Error::bad_syntax); + connection.handle_protocol_error(Error::bad_syntax, + util::format("Bad syntax in PING message: %1", e.what())); } } @@ -624,8 +624,8 @@ class ServerProtocol { auto& logger = connection.logger; auto report_error = [&](Error err, const auto fmt, auto&&... args) { - logger.error(fmt, std::forward(args)...); - connection.handle_protocol_error(err); + auto msg = util::format(fmt, std::forward(args)...); + connection.handle_protocol_error(err, std::move(msg)); }; HeaderLineParser msg(msg_data); diff --git a/src/realm/sync/noinst/server/server.cpp b/src/realm/sync/noinst/server/server.cpp index 78736073cb9..942e2049c1c 100644 --- a/src/realm/sync/noinst/server/server.cpp +++ b/src/realm/sync/noinst/server/server.cpp @@ -1275,7 +1275,7 @@ class SyncConnection : public websocket::Config { void initiate_pong_output_buffer(); - void handle_protocol_error(ServerProtocol::Error error); + void handle_protocol_error(ServerProtocol::Error error, std::string msg); void receive_bind_message(session_ident_type, std::string path, std::string signed_user_token, bool need_client_file_ident, bool is_subserver); @@ -4247,8 +4247,9 @@ void SyncConnection::enlist_to_send(Session* sess) noexcept } -void SyncConnection::handle_protocol_error(ServerProtocol::Error error) +void SyncConnection::handle_protocol_error(ServerProtocol::Error error, std::string msg) { + logger.error("%1", msg); switch (error) { case ServerProtocol::Error::unknown_message: protocol_error(ProtocolError::unknown_message); // Throws diff --git a/src/realm/sync/protocol.cpp b/src/realm/sync/protocol.cpp index 9207c335eaa..4e6e20ba6c0 100644 --- a/src/realm/sync/protocol.cpp +++ b/src/realm/sync/protocol.cpp @@ -27,6 +27,11 @@ ErrorCategoryImpl g_error_category; namespace realm { namespace sync { +Status protocol_error_to_status(ProtocolError error_code, std::string_view msg) +{ + return SystemError(make_error_code(error_code), msg).to_status(); +} + const char* get_protocol_error_message(int error_code) noexcept { // FIXME: These human-readable messages are phrased from the perspective of the client, but they may occur on the diff --git a/src/realm/sync/protocol.hpp b/src/realm/sync/protocol.hpp index 583d2074a1f..beb4bc398b4 100644 --- a/src/realm/sync/protocol.hpp +++ b/src/realm/sync/protocol.hpp @@ -367,6 +367,8 @@ constexpr bool is_session_level_error(ProtocolError); /// ProtocolError. const char* get_protocol_error_message(int error_code) noexcept; +Status protocol_error_to_status(ProtocolError error_code, std::string_view msg); + const std::error_category& protocol_error_category() noexcept; std::error_code make_error_code(ProtocolError) noexcept; diff --git a/test/object-store/c_api/c_api.cpp b/test/object-store/c_api/c_api.cpp index 955f6cc9b4e..aec86a35cc4 100644 --- a/test/object-store/c_api/c_api.cpp +++ b/test/object-store/c_api/c_api.cpp @@ -679,19 +679,19 @@ TEST_CASE("C API (non-database)", "[c_api]") { using namespace realm::sync; std::string message; - std::error_code error_code = make_error_code(sync::ClientError::connection_closed); - realm_sync_error_code_t error = c_api::to_capi(SystemError(error_code, "").to_status(), message); + Status fake_status(ErrorCodes::SyncProtocolInvariantFailed, "fake sync error"); + realm_sync_error_code_t error = c_api::to_capi(fake_status, message); CHECK(error.category == realm_sync_error_category_e::RLM_SYNC_ERROR_CATEGORY_CLIENT); - CHECK(error.value == int(error_code.value())); - CHECK(error_code.message() == error.message); + CHECK(error.value == int(ErrorCodes::SyncProtocolInvariantFailed)); + CHECK(fake_status.reason() == error.message); CHECK(message == error.message); std::error_code ec_check; c_api::sync_error_to_error_code(error, &ec_check); - CHECK(ec_check.category() == realm::sync::client_error_category()); - CHECK(ec_check.value() == int(error_code.value())); + CHECK(ec_check.category() == std::generic_category()); + CHECK(ec_check.value() == int(fake_status.code())); - error_code = make_error_code(sync::ProtocolError::connection_closed); + std::error_code error_code = make_error_code(sync::ProtocolError::connection_closed); error = c_api::to_capi(SystemError(error_code, "").to_status(), message); CHECK(error.category == realm_sync_error_category_e::RLM_SYNC_ERROR_CATEGORY_CONNECTION); diff --git a/test/object-store/sync/app.cpp b/test/object-store/sync/app.cpp index 3f6804a54e6..face49ac305 100644 --- a/test/object-store/sync/app.cpp +++ b/test/object-store/sync/app.cpp @@ -3101,7 +3101,7 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { r->commit_transaction(); auto error = wait_for_future(std::move(pf.future), std::chrono::minutes(5)).get(); - REQUIRE(error.get_system_error() == make_error_code(sync::ProtocolError::limits_exceeded)); + REQUIRE(error.status == ErrorCodes::LimitExceeded); REQUIRE(error.status.reason() == "Sync websocket closed because the server received a message that was too large: " "read limited at 16777217 bytes"); diff --git a/test/object-store/sync/client_reset.cpp b/test/object-store/sync/client_reset.cpp index bf669d0c7bf..4bc9ebbe980 100644 --- a/test/object-store/sync/client_reset.cpp +++ b/test/object-store/sync/client_reset.cpp @@ -900,9 +900,8 @@ TEST_CASE("sync: client reset", "[sync][pbs][client reset][baas]") { session = test_app_session.app()->sync_manager()->get_existing_session(temp_config.path); REQUIRE(session); } - sync::SessionErrorInfo synthetic( - Status{sync::make_error_code(sync::ProtocolError::bad_client_file), "A fake client reset error"}, - false); + sync::SessionErrorInfo synthetic(Status{ErrorCodes::SyncClientResetRequired, "A fake client reset error"}, + false); synthetic.server_requests_action = sync::ProtocolErrorInfo::Action::ClientReset; SyncSession::OnlyForTesting::handle_error(*session, std::move(synthetic)); diff --git a/test/object-store/sync/flx_sync.cpp b/test/object-store/sync/flx_sync.cpp index 377a6580de1..8ce8a311f1c 100644 --- a/test/object-store/sync/flx_sync.cpp +++ b/test/object-store/sync/flx_sync.cpp @@ -634,8 +634,7 @@ TEST_CASE("flx: client reset", "[sync][flx][client reset][baas]") { auto sync_error = wait_for_future(std::move(err_future)).get(); REQUIRE(before_reset_count == 1); REQUIRE(after_reset_count == 0); - REQUIRE(sync_error.get_system_error() == - sync::make_error_code(sync::ClientError::auto_client_reset_failure)); + REQUIRE(sync_error.status == ErrorCodes::AutoClientResetFailed); REQUIRE(sync_error.is_client_reset_requested()); local_realm->refresh(); auto table = local_realm->read_group().get_table("class_TopLevel"); @@ -681,8 +680,7 @@ TEST_CASE("flx: client reset", "[sync][flx][client reset][baas]") { auto sync_error = wait_for_future(std::move(err_future)).get(); REQUIRE(before_reset_count == 1); REQUIRE(after_reset_count == 0); - REQUIRE(sync_error.get_system_error() == - sync::make_error_code(sync::ClientError::auto_client_reset_failure)); + REQUIRE(sync_error.status == ErrorCodes::AutoClientResetFailed); REQUIRE(sync_error.is_client_reset_requested()); local_realm->refresh(); auto table = local_realm->read_group().get_table("class_TopLevel"); @@ -709,8 +707,7 @@ TEST_CASE("flx: client reset", "[sync][flx][client reset][baas]") { auto sync_error = wait_for_future(std::move(error_future2)).get(); REQUIRE(before_reset_count == 2); REQUIRE(after_reset_count == 0); - REQUIRE(sync_error.get_system_error() == - sync::make_error_code(sync::ClientError::auto_client_reset_failure)); + REQUIRE(sync_error.status == ErrorCodes::AutoClientResetFailed); REQUIRE(sync_error.is_client_reset_requested()); } @@ -850,8 +847,7 @@ TEST_CASE("flx: client reset", "[sync][flx][client reset][baas]") { ->on_post_reset([&, err_future = std::move(error_future)](SharedRealm) mutable { auto sync_error = wait_for_future(std::move(err_future)).get(); INFO(sync_error.status); - CHECK(sync_error.get_system_error() == - sync::make_error_code(sync::ClientError::auto_client_reset_failure)); + CHECK(sync_error.status == ErrorCodes::AutoClientResetFailed); }) ->run(); } @@ -884,7 +880,7 @@ TEST_CASE("flx: client reset", "[sync][flx][client reset][baas]") { // Client reset fails due to sync client not being able to create the fresh realm. auto sync_error = wait_for_future(std::move(error_future)).get(); - CHECK(sync_error.get_system_error() == sync::make_error_code(sync::ClientError::auto_client_reset_failure)); + CHECK(sync_error.status == ErrorCodes::AutoClientResetFailed); // Open the realm again. This should not crash. { @@ -894,8 +890,7 @@ TEST_CASE("flx: client reset", "[sync][flx][client reset][baas]") { auto realm_post_reset = Realm::get_shared_realm(config_copy); auto sync_error = wait_for_future(std::move(err_future)).get(); - CHECK(sync_error.get_system_error() == - sync::make_error_code(sync::ClientError::auto_client_reset_failure)); + CHECK(sync_error.status == ErrorCodes::AutoClientResetFailed); } } @@ -2340,6 +2335,7 @@ TEST_CASE("flx: connect to PBS as FLX returns an error", "[sync][flx][protocol][ return static_cast(sync_error); }); + CHECK(sync_error->get_system_error() == make_error_code(sync::ProtocolError::switch_to_pbs)); CHECK(sync_error->server_requests_action == sync::ProtocolErrorInfo::Action::ApplicationBug); } @@ -2494,7 +2490,7 @@ TEST_CASE("flx: bootstrap batching prevents orphan documents", "[sync][flx][boot auto realm = Realm::get_shared_realm(interrupted_realm_config); const auto& error = error_pf.future.get(); REQUIRE(error.is_fatal); - REQUIRE(error.get_system_error() == make_error_code(sync::ClientError::bad_changeset)); + REQUIRE(error.status == ErrorCodes::BadChangeset); } SECTION("interrupted before final bootstrap message") { @@ -2819,7 +2815,7 @@ TEST_CASE("flx: data ingest", "[sync][flx][data ingest][baas]") { ++error_count; if (error_count == 1) { // Bad changeset detected by the client. - CHECK(err.get_system_error() == sync::make_error_code(sync::ClientError::bad_changeset)); + CHECK(err.status == ErrorCodes::BadChangeset); } else if (error_count == 2) { // Server asking for a client reset. @@ -2961,7 +2957,7 @@ TEST_CASE("flx: send client error", "[sync][flx][baas]") { ++error_count; if (error_count == 1) { // Bad changeset detected by the client. - CHECK(err.get_system_error() == sync::make_error_code(sync::ClientError::bad_changeset)); + CHECK(err.status == ErrorCodes::BadChangeset); } else if (error_count == 2) { // Server asking for a client reset. diff --git a/test/sync_fixtures.hpp b/test/sync_fixtures.hpp index 5363cef056d..d683448cb24 100644 --- a/test/sync_fixtures.hpp +++ b/test/sync_fixtures.hpp @@ -581,24 +581,21 @@ class MultiClientServerFixture { } } - using ErrorHandler = void(std::error_code ec, bool is_fatal, const std::string& detailed_message); + using ErrorHandler = void(Status status, bool is_fatal, const std::string& detailed_message); // Set an error handler to be used for all sessions of the specified client // (\a handler will be copied for each session). Must be called before // make_session(). void set_client_side_error_handler(int client_index, std::function handler) { - auto handler_2 = [handler = std::move(handler)](ConnectionState state, - std::optional error_info) { + auto handler_wrapped = [handler = std::move(handler)](ConnectionState state, + std::optional error_info) { if (state != ConnectionState::disconnected) return; REALM_ASSERT(error_info); - std::error_code ec = error_info->status.get_std_error_code(); - bool is_fatal = error_info->is_fatal(); - const std::string& detailed_message = error_info->message; - handler(ec, is_fatal, detailed_message); + handler(error_info->status, error_info->is_fatal(), error_info->message); }; - m_connection_state_change_listeners[client_index] = std::move(handler_2); + m_connection_state_change_listeners[client_index] = std::move(handler_wrapped); } void set_client_side_error_rate(int client_index, int n, int m) @@ -1081,10 +1078,7 @@ inline void RealmFixture::setup_error_handler(util::UniqueFunction if (state != ConnectionState::disconnected) return; REALM_ASSERT(error_info); - std::error_code ec = error_info->status.get_std_error_code(); - bool is_fatal = error_info->is_fatal(); - const std::string& detailed_message = error_info->message; - handler(ec, is_fatal, detailed_message); + handler(error_info->status, error_info->is_fatal(), error_info->message); }; m_session.set_connection_state_change_listener(std::move(listener)); } diff --git a/test/test_client_reset.cpp b/test/test_client_reset.cpp index 339f9376219..34ae8f20210 100644 --- a/test/test_client_reset.cpp +++ b/test/test_client_reset.cpp @@ -775,8 +775,7 @@ TEST(ClientReset_DoNotRecoverSchema) if (state != ConnectionState::disconnected) return; REALM_ASSERT(error_info); - std::error_code ec = error_info->status.get_std_error_code(); - CHECK_EQUAL(ec, sync::Client::Error::auto_client_reset_failure); + CHECK_EQUAL(error_info->status, ErrorCodes::AutoClientResetFailed); bowl.add_stone(); }); session.bind(); diff --git a/test/test_sync.cpp b/test/test_sync.cpp index de743189971..5e877f2e5a7 100644 --- a/test/test_sync.cpp +++ b/test/test_sync.cpp @@ -603,7 +603,7 @@ TEST(Sync_TokenWithNullExpirationAllowed) TEST_DIR(dir); TEST_CLIENT_DB(db); ClientServerFixture fixture(dir, test_context); - auto error_handler = [&](std::error_code, bool, const std::string&) { + auto error_handler = [&](Status, bool, const std::string&) { did_fail = true; fixture.stop(); }; @@ -785,13 +785,10 @@ struct ExpectChangesetError { if (!error_info) return; REALM_ASSERT(error_info); - std::error_code ec = error_info->status.get_std_error_code(); - CHECK_EQUAL(ec, sync::Client::Error::bad_changeset); - CHECK(ec.category() == client_error_category()); + CHECK_EQUAL(error_info->status, ErrorCodes::BadChangeset); CHECK(!error_info->is_fatal()); CHECK_EQUAL(error_info->message, - "Bad changeset (DOWNLOAD): Failed to transform received changeset: Schema mismatch: " + - expected_error); + "Failed to transform received changeset: Schema mismatch: " + expected_error); fixture.stop(); } }; @@ -1479,7 +1476,8 @@ TEST(Sync_ReadFailureSimulation) { ClientServerFixture fixture(server_dir, test_context); fixture.set_client_side_error_rate(1, 1); // 100% chance of failure - auto error_handler = [&](std::error_code ec, bool is_fatal, const std::string&) { + auto error_handler = [&](Status status, bool is_fatal, const std::string&) { + auto ec = status.get_std_error_code(); CHECK_EQUAL(_impl::SimulatedFailure::sync_client__read_head, ec); CHECK_NOT(is_fatal); client_side_read_did_fail = true; @@ -1509,7 +1507,8 @@ TEST(Sync_FailingReadsOnClientSide) TEST_DIR(dir); ClientServerFixture fixture{dir, test_context}; fixture.set_client_side_error_rate(5, 100); // 5% chance of failure - auto error_handler = [&](std::error_code ec, bool, const std::string&) { + auto error_handler = [&](Status status, bool, const std::string&) { + auto ec = status.get_std_error_code(); if (CHECK_EQUAL(_impl::SimulatedFailure::sync_client__read_head, ec)) fixture.cancel_reconnect_delay(); }; @@ -1569,7 +1568,7 @@ TEST(Sync_FailingReadsOnServerSide) TEST_DIR(dir); ClientServerFixture fixture{dir, test_context}; fixture.set_server_side_error_rate(5, 100); // 5% chance of failure - auto error_handler = [&](std::error_code, bool is_fatal, const std::string&) { + auto error_handler = [&](Status, bool is_fatal, const std::string&) { CHECK_NOT(is_fatal); fixture.cancel_reconnect_delay(); }; @@ -1648,8 +1647,8 @@ TEST(Sync_ErrorAfterServerRestore_BadClientFileIdent) bool did_fail = false; { ClientServerFixture fixture(server_dir, test_context); - auto error_handler = [&](std::error_code ec, bool is_fatal, const std::string&) { - CHECK_EQUAL(ProtocolError::bad_server_version, ec); + auto error_handler = [&](Status status, bool is_fatal, const std::string&) { + CHECK_EQUAL(ProtocolError::bad_server_version, status.get_std_error_code()); CHECK(is_fatal); did_fail = true; fixture.stop(); @@ -1858,8 +1857,8 @@ TEST(Sync_ErrorAfterServerRestore_BadServerVersion) bool did_fail = false; { ClientServerFixture fixture(server_dir, test_context); - auto error_handler = [&](std::error_code ec, bool is_fatal, const std::string&) { - CHECK_EQUAL(ProtocolError::bad_server_version, ec); + auto error_handler = [&](Status status, bool is_fatal, const std::string&) { + CHECK_EQUAL(ProtocolError::bad_server_version, status.get_std_error_code()); CHECK(is_fatal); did_fail = true; fixture.stop(); @@ -1936,8 +1935,8 @@ TEST(Sync_ErrorAfterServerRestore_BadClientVersion) bool did_fail = false; { ClientServerFixture fixture(server_dir, test_context); - auto error_handler = [&](std::error_code ec, bool is_fatal, const std::string&) { - CHECK_EQUAL(ProtocolError::bad_client_version, ec); + auto error_handler = [&](Status status, bool is_fatal, const std::string&) { + CHECK_EQUAL(ProtocolError::bad_client_version, status.get_std_error_code()); CHECK(is_fatal); did_fail = true; fixture.stop(); @@ -2003,8 +2002,8 @@ TEST(Sync_ErrorAfterServerRestore_BadClientFileIdentSalt) bool did_fail = false; { ClientServerFixture fixture(server_dir, test_context); - auto error_handler = [&](std::error_code ec, bool is_fatal, const std::string&) { - CHECK_EQUAL(ProtocolError::diverging_histories, ec); + auto error_handler = [&](Status status, bool is_fatal, const std::string&) { + CHECK_EQUAL(ProtocolError::diverging_histories, status.get_std_error_code()); CHECK(is_fatal); did_fail = true; fixture.stop(); @@ -2087,8 +2086,8 @@ TEST(Sync_ErrorAfterServerRestore_BadServerVersionSalt) bool did_fail = false; { ClientServerFixture fixture(server_dir, test_context); - auto error_handler = [&](std::error_code ec, bool is_fatal, const std::string&) { - CHECK_EQUAL(ProtocolError::diverging_histories, ec); + auto error_handler = [&](Status status, bool is_fatal, const std::string&) { + CHECK_EQUAL(ProtocolError::diverging_histories, status.get_std_error_code()); CHECK(is_fatal); did_fail = true; fixture.stop(); @@ -2272,8 +2271,8 @@ TEST_IF(Sync_ReadOnlyClient, false) TEST_DIR(server_dir); MultiClientServerFixture fixture(2, 1, server_dir, test_context); bool did_get_permission_denied = false; - fixture.set_client_side_error_handler(1, [&](std::error_code ec, bool, const std::string&) { - CHECK_EQUAL(ProtocolError::permission_denied, ec); + fixture.set_client_side_error_handler(1, [&](Status status, bool, const std::string&) { + CHECK_EQUAL(ProtocolError::permission_denied, status.get_std_error_code()); did_get_permission_denied = true; fixture.get_client(1).shutdown(); }); @@ -2666,7 +2665,7 @@ TEST(Sync_Permissions) TEST_DIR(server_dir); ClientServerFixture fixture{server_dir, test_context}; - fixture.set_client_side_error_handler([&](std::error_code, bool, const std::string& message) { + fixture.set_client_side_error_handler([&](Status, bool, const std::string& message) { CHECK_EQUAL("", message); did_see_error_for_valid = true; }); @@ -2736,8 +2735,8 @@ TEST(Sync_SSL_Certificate_2) session_config.verify_servers_ssl_certificate = true; session_config.ssl_trust_certificate_path = ca_dir + "dns-chain.crt.pem"; - auto error_handler = [&](std::error_code ec, bool, const std::string&) { - CHECK_EQUAL(ec, Client::Error::ssl_server_cert_rejected); + auto error_handler = [&](Status status, bool, const std::string&) { + CHECK_EQUAL(status, ErrorCodes::TlsHandshakeFailed); did_fail = true; fixture.stop(); }; @@ -2887,8 +2886,8 @@ TEST(Sync_SSL_Certificate_Verify_Callback_2) ClientServerFixture fixture{server_dir, test_context, config}; - auto error_handler = [&](std::error_code ec, bool, const std::string&) { - CHECK_EQUAL(ec, Client::Error::ssl_server_cert_rejected); + auto error_handler = [&](Status status, bool, const std::string&) { + CHECK_EQUAL(status, ErrorCodes::TlsHandshakeFailed); did_fail = true; fixture.stop(); }; @@ -4493,8 +4492,8 @@ TEST(Sync_PingTimesOut) config.client_pong_timeout = 0; // time out immediately ClientServerFixture fixture(dir, test_context, std::move(config)); - auto error_handler = [&](std::error_code ec, bool, const std::string&) { - CHECK_EQUAL(Client::Error::pong_timeout, ec); + auto error_handler = [&](Status status, bool, const std::string&) { + CHECK_EQUAL(status, ErrorCodes::ConnectionClosed); did_fail = true; fixture.stop(); }; @@ -4521,8 +4520,8 @@ TEST(Sync_ReconnectAfterPingTimeout) ClientServerFixture fixture(dir, test_context, std::move(config)); BowlOfStonesSemaphore bowl; - auto error_handler = [&](std::error_code ec, bool, const std::string&) { - if (CHECK_EQUAL(Client::Error::pong_timeout, ec)) + auto error_handler = [&](Status status, bool, const std::string&) { + if (CHECK_EQUAL(status, ErrorCodes::ConnectionClosed)) bowl.add_stone(); }; fixture.set_client_side_error_handler(std::move(error_handler)); @@ -4545,8 +4544,8 @@ TEST(Sync_UrgentPingIsSent) ClientServerFixture fixture(dir, test_context, std::move(config)); - auto error_handler = [&](std::error_code ec, bool, const std::string&) { - CHECK_EQUAL(Client::Error::pong_timeout, ec); + auto error_handler = [&](Status status, bool, const std::string&) { + CHECK_EQUAL(status, ErrorCodes::ConnectionClosed); did_fail = true; fixture.stop(); }; @@ -4574,7 +4573,8 @@ TEST(Sync_ServerDiscardDeadConnections) ClientServerFixture fixture(dir, test_context, std::move(config)); BowlOfStonesSemaphore bowl; - auto error_handler = [&](std::error_code ec, bool, const std::string&) { + auto error_handler = [&](Status status, bool, const std::string&) { + auto ec = status.get_std_error_code(); bool valid_error = (ec == sync::websocket::WebSocketError::websocket_read_error); CHECK(valid_error); bowl.add_stone(); @@ -5174,7 +5174,7 @@ TEST_IF(Sync_SSL_Certificates, false) "State change: disconnected, error_code = %1, is_fatal = %2, detailed_message = %3", error_info->status.get_std_error_code(), error_info->is_fatal(), error_info->message); // We expect to get through the SSL handshake but will hit an error due to the wrong token. - CHECK_NOT_EQUAL(error_info->status.get_std_error_code(), Client::Error::ssl_server_cert_rejected); + CHECK_NOT_EQUAL(error_info->status, ErrorCodes::TlsHandshakeFailed); client.shutdown(); } }; From 4fe8d93500435f89b4bf47612e60b7e5eb68b600 Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Wed, 2 Aug 2023 10:21:16 -0400 Subject: [PATCH 11/30] fixes from PR --- src/realm/sync/noinst/client_history_impl.cpp | 55 +++++++++++-------- src/realm/sync/noinst/client_history_impl.hpp | 2 +- src/realm/sync/noinst/client_impl_base.cpp | 13 +++-- 3 files changed, 41 insertions(+), 29 deletions(-) diff --git a/src/realm/sync/noinst/client_history_impl.cpp b/src/realm/sync/noinst/client_history_impl.cpp index 4a6fe0e9962..21b102363a2 100644 --- a/src/realm/sync/noinst/client_history_impl.cpp +++ b/src/realm/sync/noinst/client_history_impl.cpp @@ -801,34 +801,45 @@ void ClientHistory::update_sync_progress(const SyncProgress& progress, const std Array& root = m_arrays->root; // Progress must never decrease - if (progress.latest_server_version.version < - version_type(root.get_as_ref_or_tagged(s_progress_latest_server_version_iip).get_as_int())) { - throw IntegrationException(ErrorCodes::SyncProtocolInvariantFailed, "latest server version cannot decrease", + if (auto current = version_type(root.get_as_ref_or_tagged(s_progress_latest_server_version_iip).get_as_int()); + progress.latest_server_version.version < current) { + throw IntegrationException(ErrorCodes::SyncProtocolInvariantFailed, + util::format("latest server version cannot decrease (current: %1, new: %2)", + current, progress.latest_server_version.version), ProtocolError::bad_progress); } - if (progress.download.server_version < - version_type(root.get_as_ref_or_tagged(s_progress_download_server_version_iip).get_as_int())) { - throw IntegrationException(ErrorCodes::SyncProtocolInvariantFailed, - "server version of download cursor cannot decrease", ProtocolError::bad_progress); + if (auto current = version_type(root.get_as_ref_or_tagged(s_progress_download_server_version_iip).get_as_int()); + progress.download.server_version < current) { + throw IntegrationException( + ErrorCodes::SyncProtocolInvariantFailed, + util::format("server version of download cursor cannot decrease (current: %1, new: %2)", current, + progress.download.server_version), + ProtocolError::bad_progress); } - if (progress.download.last_integrated_client_version < - version_type(root.get_as_ref_or_tagged(s_progress_download_client_version_iip).get_as_int())) { - throw IntegrationException(ErrorCodes::SyncProtocolInvariantFailed, - "last integrated client version of download cursor cannot decrease", - ProtocolError::bad_progress); + if (auto current = version_type(root.get_as_ref_or_tagged(s_progress_download_client_version_iip).get_as_int()); + progress.download.last_integrated_client_version < current) { + throw IntegrationException( + ErrorCodes::SyncProtocolInvariantFailed, + util::format("last integrated client version of download cursor cannot decrease (current: %1, new: %2)", + current, progress.download.last_integrated_client_version), + ProtocolError::bad_progress); } - if (progress.upload.client_version < - version_type(root.get_as_ref_or_tagged(s_progress_upload_client_version_iip).get_as_int())) { - throw IntegrationException(ErrorCodes::SyncProtocolInvariantFailed, - "client version of upload cursor cannot decrease", ProtocolError::bad_progress); + if (auto current = version_type(root.get_as_ref_or_tagged(s_progress_upload_client_version_iip).get_as_int()); + progress.upload.client_version < current) { + throw IntegrationException( + ErrorCodes::SyncProtocolInvariantFailed, + util::format("client version of upload cursor cannot decrease (current: %1, new: %2)", current, + progress.upload.client_version), + ProtocolError::bad_progress); } const auto last_integrated_server_version = progress.upload.last_integrated_server_version; - if (last_integrated_server_version > 0 && - last_integrated_server_version < - version_type(root.get_as_ref_or_tagged(s_progress_upload_server_version_iip).get_as_int())) { - throw IntegrationException(ErrorCodes::SyncProtocolInvariantFailed, - "last integrated server version of upload cursor cannot decrease", - ProtocolError::bad_progress); + if (auto current = version_type(root.get_as_ref_or_tagged(s_progress_upload_server_version_iip).get_as_int()); + last_integrated_server_version > 0 && last_integrated_server_version < current) { + throw IntegrationException( + ErrorCodes::SyncProtocolInvariantFailed, + util::format("last integrated server version of upload cursor cannot decrease (current: %1, new: %2)", + current, last_integrated_server_version), + ProtocolError::bad_progress); } auto uploaded_bytes = std::uint_fast64_t(root.get_as_ref_or_tagged(s_progress_uploaded_bytes_iip).get_as_int()); diff --git a/src/realm/sync/noinst/client_history_impl.hpp b/src/realm/sync/noinst/client_history_impl.hpp index 2b293296408..42244b69f0f 100644 --- a/src/realm/sync/noinst/client_history_impl.hpp +++ b/src/realm/sync/noinst/client_history_impl.hpp @@ -76,7 +76,7 @@ class IntegrationException : public RuntimeError { { } - ProtocolError error_for_server = ProtocolError::other_session_error; + ProtocolError error_for_server; }; class ClientHistory final : public _impl::History, public TransformHistory { diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp index 053d519dc3c..c9b00ed8946 100644 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ b/src/realm/sync/noinst/client_impl_base.cpp @@ -1116,7 +1116,7 @@ void Connection::close_due_to_client_side_error(std::error_code ec, std::optiona void Connection::close_due_to_client_side_error(Status status, IsFatal is_fatal, ConnectionTerminationReason reason) { - logger.info("Connection closed due to error"); // Throws + logger.info("Connection closed due to error: %1", status); // Throws const bool try_again = !is_fatal; involuntary_disconnect(SessionErrorInfo{std::move(status), try_again}, reason); // Throw @@ -1124,7 +1124,7 @@ void Connection::close_due_to_client_side_error(Status status, IsFatal is_fatal, void Connection::close_due_to_transient_error(Status status, ConnectionTerminationReason reason) { - logger.info("Connection closed due to error"); // Throws + logger.info("Connection closed due to transient error: %1", status); // Throws SessionErrorInfo error_info{std::move(status), true}; error_info.server_requests_action = ProtocolErrorInfo::Action::Transient; @@ -2434,10 +2434,11 @@ Status Session::receive_mark_message(request_ident_type request_ident) bool good_request_ident = (request_ident <= m_last_download_mark_sent && request_ident > m_last_download_mark_received); if (REALM_UNLIKELY(!good_request_ident)) { - return {ErrorCodes::SyncProtocolInvariantFailed, - util::format( - "Received MARK message with invalid request identifer (last mark sent: %1 last mark received: %2", - m_last_download_mark_sent, m_last_download_mark_received)}; + return { + ErrorCodes::SyncProtocolInvariantFailed, + util::format( + "Received MARK message with invalid request identifer (last mark sent: %1 last mark received: %2)", + m_last_download_mark_sent, m_last_download_mark_received)}; } m_server_version_at_last_download_mark = m_progress.download.server_version; From c53c6c882163adc5a76a5dc6579ee5d5837768bd Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Thu, 3 Aug 2023 16:19:27 -0400 Subject: [PATCH 12/30] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a92769880a..ce13ce954c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ * `wait_for_upload_completion`/`wait_for_download_completion` internal API was changed to use `Status`'s instead of `std::error_code`. The SDK-facing was already `Status` oriented, so this change should only result in better error messages. ([PR #6796](https://github.com/realm/realm-core/pull/6796)) * Separate local and baas object store tests into separate evergreen tasks and allow custom test specification. ([PR #6805](https://github.com/realm/realm-core/pull/6805)) * SyncError now contains a Status to hold the error information from the sync client instead of a `std::error_code`/`std::string` ([PR #6824](https://github.com/realm/realm-core/pull/6824)). +* The ClientError enum/`std::error_code` in the sync client has been removed in favor of a simplified error set using Status/ErrorCodes ([PR #6846](https://github.com/realm/realm-core/pull/6846)). ---------------------------------------------- From 753f2ec625f18351888bfd0a18bf22cf182bb1dd Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Thu, 3 Aug 2023 17:56:10 -0400 Subject: [PATCH 13/30] Handle websocket errors entirely within sync client --- src/realm.h | 4 +- .../object-store/c_api/socket_provider.cpp | 21 +- src/realm/object-store/c_api/sync.cpp | 6 - src/realm/object-store/sync/sync_session.cpp | 49 +-- src/realm/sync/network/default_socket.cpp | 49 +-- src/realm/sync/network/websocket.cpp | 213 +++++----- src/realm/sync/network/websocket.hpp | 45 +-- src/realm/sync/noinst/client_impl_base.cpp | 363 ++++++++++-------- src/realm/sync/noinst/client_impl_base.hpp | 5 +- src/realm/sync/noinst/protocol_codec.hpp | 4 +- src/realm/sync/protocol.hpp | 23 +- src/realm/sync/socket_provider.hpp | 8 +- test/object-store/c_api/c_api.cpp | 16 +- test/object-store/sync/client_reset.cpp | 4 - test/sync_fixtures.hpp | 6 +- test/test_sync.cpp | 13 +- test/test_util_websocket.cpp | 7 +- 17 files changed, 393 insertions(+), 443 deletions(-) diff --git a/src/realm.h b/src/realm.h index 9705486d4f9..3966683bafa 100644 --- a/src/realm.h +++ b/src/realm.h @@ -4106,8 +4106,8 @@ RLM_API realm_sync_socket_t* realm_sync_socket_new( realm_sync_socket_websocket_async_write_func_t websocket_write_func, realm_sync_socket_websocket_free_func_t websocket_free_func); -RLM_API void realm_sync_socket_callback_complete(realm_sync_socket_callback_t* realm_callback, - realm_web_socket_errno_e status, const char* reason); +RLM_API void realm_sync_socket_callback_complete(realm_sync_socket_callback_t* realm_callback, realm_errno_e status, + const char* reason); RLM_API void realm_sync_socket_websocket_connected(realm_websocket_observer_t* realm_websocket_observer, const char* protocol); diff --git a/src/realm/object-store/c_api/socket_provider.cpp b/src/realm/object-store/c_api/socket_provider.cpp index a43d4fa1a87..47c005005b5 100644 --- a/src/realm/object-store/c_api/socket_provider.cpp +++ b/src/realm/object-store/c_api/socket_provider.cpp @@ -122,9 +122,9 @@ struct CAPIWebSocketObserver : sync::WebSocketObserver { return m_observer->websocket_binary_message_received(data); } - bool websocket_closed_handler(bool was_clean, Status status) final + bool websocket_closed_handler(bool was_clean, sync::websocket::WebSocketError code, std::string_view msg) final { - return m_observer->websocket_closed_handler(was_clean, status); + return m_observer->websocket_closed_handler(was_clean, code, msg); } private: @@ -218,13 +218,11 @@ RLM_API realm_sync_socket_t* realm_sync_socket_new( }); } -RLM_API void realm_sync_socket_callback_complete(realm_sync_socket_callback* realm_callback, - realm_web_socket_errno_e code, const char* reason) +RLM_API void realm_sync_socket_callback_complete(realm_sync_socket_callback* realm_callback, realm_errno_e code, + const char* reason) { - auto status = sync::websocket::WebSocketError(code); - auto complete_status = code == realm_web_socket_errno_e::RLM_ERR_WEBSOCKET_OK - ? Status::OK() - : Status{sync::websocket::make_error_code(status), reason}; + auto complete_status = + code == realm_errno_e::RLM_ERR_NONE ? Status::OK() : Status{static_cast(code), reason}; (*(realm_callback->get()))(complete_status); realm_release(realm_callback); } @@ -249,11 +247,8 @@ RLM_API void realm_sync_socket_websocket_message(realm_websocket_observer_t* rea RLM_API void realm_sync_socket_websocket_closed(realm_websocket_observer_t* realm_websocket_observer, bool was_clean, realm_web_socket_errno_e code, const char* reason) { - auto status = sync::websocket::WebSocketError(code); - auto closed_status = code == realm_web_socket_errno_e::RLM_ERR_WEBSOCKET_OK - ? Status::OK() - : Status{sync::websocket::make_error_code(status), reason}; - realm_websocket_observer->get()->websocket_closed_handler(was_clean, closed_status); + realm_websocket_observer->get()->websocket_closed_handler( + was_clean, static_cast(code), reason); } RLM_API void realm_sync_client_config_set_sync_socket(realm_sync_client_config_t* config, diff --git a/src/realm/object-store/c_api/sync.cpp b/src/realm/object-store/c_api/sync.cpp index 945d5fbb1e8..39f39281019 100644 --- a/src/realm/object-store/c_api/sync.cpp +++ b/src/realm/object-store/c_api/sync.cpp @@ -144,9 +144,6 @@ realm_sync_error_code_t to_capi(const Status& status, std::string& message) else if (category == std::system_category() || category == realm::util::error::basic_system_error_category()) { ret.category = RLM_SYNC_ERROR_CATEGORY_SYSTEM; } - else if (category == realm::sync::websocket::websocket_error_category()) { - ret.category = RLM_SYNC_ERROR_CATEGORY_WEBSOCKET; - } else { ret.category = RLM_SYNC_ERROR_CATEGORY_UNKNOWN; } @@ -178,9 +175,6 @@ void sync_error_to_error_code(const realm_sync_error_code_t& sync_error_code, st else if (category == RLM_SYNC_ERROR_CATEGORY_SYSTEM) { error_code_out->assign(sync_error_code.value, std::system_category()); } - else if (category == RLM_SYNC_ERROR_CATEGORY_WEBSOCKET) { - error_code_out->assign(sync_error_code.value, realm::sync::websocket::websocket_error_category()); - } else if (category == RLM_SYNC_ERROR_CATEGORY_UNKNOWN) { error_code_out->assign(sync_error_code.value, realm::util::error::basic_system_error_category()); } diff --git a/src/realm/object-store/sync/sync_session.cpp b/src/realm/object-store/sync/sync_session.cpp index 52e99809074..26366e2473d 100644 --- a/src/realm/object-store/sync/sync_session.cpp +++ b/src/realm/object-store/sync/sync_session.cpp @@ -633,7 +633,7 @@ void SyncSession::OnlyForTesting::handle_error(SyncSession& session, sync::Sessi void SyncSession::handle_error(sync::SessionErrorInfo error) { enum class NextStateAfterError { none, inactive, error }; - auto next_state = error.is_fatal() ? NextStateAfterError::error : NextStateAfterError::none; + auto next_state = error.is_fatal ? NextStateAfterError::error : NextStateAfterError::none; auto error_code = error.status.get_std_error_code(); util::Optional delete_file; bool log_out_user = false; @@ -708,46 +708,27 @@ void SyncSession::handle_error(sync::SessionErrorInfo error) save_sync_config_after_migration_or_rollback(); download_fresh_realm(error.server_requests_action); return; + case sync::ProtocolErrorInfo::Action::RefreshUser: + if (auto u = user()) { + u->refresh_custom_data(false, handle_refresh(shared_from_this(), false)); + return; + } + break; + case sync::ProtocolErrorInfo::Action::RefreshLocation: + if (auto u = user()) { + u->refresh_custom_data(true, handle_refresh(shared_from_this(), true)); + return; + } + break; } } - else if (error_code.category() == sync::websocket::websocket_error_category()) { - using WebSocketError = sync::websocket::WebSocketError; - auto websocket_error = static_cast(error_code.value()); - - // The server replies with '401: unauthorized' if the access token is invalid, expired, revoked, or the user - // is disabled. In this scenario we attempt an automatic token refresh and if that succeeds continue as - // normal. If the refresh request also fails with 401 then we need to stop retrying and pass along the error; - // see handle_refresh(). - bool redirect_occurred = websocket_error == WebSocketError::websocket_moved_permanently; - if (redirect_occurred || websocket_error == WebSocketError::websocket_unauthorized || - websocket_error == WebSocketError::websocket_abnormal_closure) { - if (auto u = user()) { - // If a redirection occurred, the location metadata will be updated before refreshing the access - // token. - u->refresh_custom_data(redirect_occurred, handle_refresh(shared_from_this(), redirect_occurred)); - return; - } - } - - // If the websocket was closed cleanly or if the socket disappeared, don't notify the user as an error - // since the sync client will retry. - if (websocket_error == WebSocketError::websocket_read_error || - websocket_error == WebSocketError::websocket_write_error) { - return; - } - - // Surface a simplified websocket error to the user. - auto simplified_error = sync::websocket::get_simplified_websocket_error(websocket_error); - std::error_code new_error_code(simplified_error, sync::websocket::websocket_error_category()); - error = sync::SessionErrorInfo(Status{new_error_code, error.message}, error.try_again); - } else { // Unrecognized error code. unrecognized_by_client = true; } util::CheckedUniqueLock lock(m_state_mutex); - SyncError sync_error{error.status, error.is_fatal(), error.log_url, std::move(error.compensating_writes)}; + SyncError sync_error{error.status, error.is_fatal, error.log_url, std::move(error.compensating_writes)}; // `action` is used over `shouldClientReset` and `isRecoveryModeDisabled`. sync_error.server_requests_action = error.server_requests_action; sync_error.is_unrecognized_by_client = unrecognized_by_client; @@ -755,7 +736,7 @@ void SyncSession::handle_error(sync::SessionErrorInfo error) if (delete_file) update_error_and_mark_file_for_deletion(sync_error, *delete_file); - if (m_state == State::Dying && error.is_fatal()) { + if (m_state == State::Dying && error.is_fatal) { become_inactive(std::move(lock), error.status); return; } diff --git a/src/realm/sync/network/default_socket.cpp b/src/realm/sync/network/default_socket.cpp index 9032bc3cd11..e7617afb671 100644 --- a/src/realm/sync/network/default_socket.cpp +++ b/src/realm/sync/network/default_socket.cpp @@ -78,15 +78,13 @@ class DefaultWebSocketImpl final : public DefaultWebSocket, public Config { { m_logger.error("Reading failed: %1", ec.message()); // Throws constexpr bool was_clean = false; - websocket_error_and_close_handler( - was_clean, Status{make_error_code(WebSocketError::websocket_read_error), ec.message()}); + websocket_error_and_close_handler(was_clean, WebSocketError::websocket_read_error, ec.message()); } void websocket_write_error_handler(std::error_code ec) override { m_logger.error("Writing failed: %1", ec.message()); // Throws constexpr bool was_clean = false; - websocket_error_and_close_handler( - was_clean, Status{make_error_code(WebSocketError::websocket_write_error), ec.message()}); + websocket_error_and_close_handler(was_clean, WebSocketError::websocket_write_error, ec.message()); } void websocket_handshake_error_handler(std::error_code ec, const HTTPHeaders*, const std::string_view* body) override @@ -144,30 +142,25 @@ class DefaultWebSocketImpl final : public DefaultWebSocket, public Config { } } - websocket_error_and_close_handler(was_clean, Status{make_error_code(error), ec.message()}); + websocket_error_and_close_handler(was_clean, error, ec.message()); } void websocket_protocol_error_handler(std::error_code ec) override { constexpr bool was_clean = false; - websocket_error_and_close_handler( - was_clean, Status{make_error_code(WebSocketError::websocket_protocol_error), ec.message()}); + websocket_error_and_close_handler(was_clean, WebSocketError::websocket_protocol_error, ec.message()); } - bool websocket_close_message_received(std::error_code ec, StringData message) override + bool websocket_close_message_received(WebSocketError code, std::string_view message) override { constexpr bool was_clean = true; - // Normal closure. - if (ec.value() == 1000) { - return websocket_error_and_close_handler(was_clean, Status::OK()); - } - return websocket_error_and_close_handler(was_clean, Status{ec, message}); + return websocket_error_and_close_handler(was_clean, code, message); } - bool websocket_error_and_close_handler(bool was_clean, Status status) + bool websocket_error_and_close_handler(bool was_clean, WebSocketError code, std::string_view reason) { if (!was_clean) { m_observer->websocket_error_handler(); } - return m_observer->websocket_closed_handler(was_clean, status); + return m_observer->websocket_closed_handler(was_clean, code, reason); } bool websocket_binary_message_received(const char* ptr, std::size_t size) override { @@ -275,8 +268,8 @@ void DefaultWebSocketImpl::handle_resolve(std::error_code ec, network::Endpoint: if (ec) { m_logger.error("Failed to resolve '%1:%2': %3", m_endpoint.address, m_endpoint.port, ec.message()); // Throws constexpr bool was_clean = false; - websocket_error_and_close_handler( - was_clean, Status{make_error_code(WebSocketError::websocket_resolve_failed), ec.message()}); // Throws + websocket_error_and_close_handler(was_clean, WebSocketError::websocket_resolve_failed, + ec.message()); // Throws return; } @@ -316,8 +309,8 @@ void DefaultWebSocketImpl::handle_tcp_connect(std::error_code ec, network::Endpo // All endpoints failed m_logger.error("Failed to connect to '%1:%2': All endpoints failed", m_endpoint.address, m_endpoint.port); constexpr bool was_clean = false; - websocket_error_and_close_handler( - was_clean, Status{make_error_code(WebSocketError::websocket_connection_failed), ec.message()}); // Throws + websocket_error_and_close_handler(was_clean, WebSocketError::websocket_connection_failed, + ec.message()); // Throws return; } @@ -357,18 +350,16 @@ void DefaultWebSocketImpl::initiate_http_tunnel() if (ec && ec != util::error::operation_aborted) { m_logger.error("Failed to establish HTTP tunnel: %1", ec.message()); constexpr bool was_clean = false; - websocket_error_and_close_handler( - was_clean, - Status{make_error_code(WebSocketError::websocket_connection_failed), ec.message()}); // Throws + websocket_error_and_close_handler(was_clean, WebSocketError::websocket_connection_failed, + ec.message()); // Throws return; } if (response.status != HTTPStatus::Ok) { m_logger.error("Proxy server returned response '%1 %2'", response.status, response.reason); // Throws constexpr bool was_clean = false; - websocket_error_and_close_handler( - was_clean, - Status{make_error_code(WebSocketError::websocket_connection_failed), response.reason}); // Throws + websocket_error_and_close_handler(was_clean, WebSocketError::websocket_connection_failed, + response.reason); // Throws return; } @@ -431,15 +422,15 @@ void DefaultWebSocketImpl::handle_ssl_handshake(std::error_code ec) if (ec) { REALM_ASSERT(ec != util::error::operation_aborted); constexpr bool was_clean = false; - std::error_code ec2; + WebSocketError parsed_error_code; if (ec == network::ssl::Errors::certificate_rejected) { - ec2 = make_error_code(WebSocketError::websocket_tls_handshake_failed); + parsed_error_code = WebSocketError::websocket_tls_handshake_failed; } else { - ec2 = make_error_code(WebSocketError::websocket_connection_failed); + parsed_error_code = WebSocketError::websocket_connection_failed; } - websocket_error_and_close_handler(was_clean, Status{ec2, ec.message()}); // Throws + websocket_error_and_close_handler(was_clean, parsed_error_code, ec.message()); // Throws return; } diff --git a/src/realm/sync/network/websocket.cpp b/src/realm/sync/network/websocket.cpp index e91d982598d..2e815c90bd3 100644 --- a/src/realm/sync/network/websocket.cpp +++ b/src/realm/sync/network/websocket.cpp @@ -944,10 +944,10 @@ class WebSocket { return true; } - std::pair parse_close_message(const char* data, size_t size) + std::pair parse_close_message(const char* data, size_t size) { uint16_t error_code; - StringData error_message; + std::string_view error_message; if (size < 2) { // Error code 1005 is defined as // 1005 is a reserved value and MUST NOT be set as a status code in a @@ -962,11 +962,36 @@ class WebSocket { // network byte order. See https://tools.ietf.org/html/rfc6455#section-5.5.1 for more // details. error_code = ntohs((uint8_t(data[1]) << 8) | uint8_t(data[0])); - error_message = StringData(data + 2, size - 2); + error_message = std::string_view(data + 2, size - 2); } - std::error_code error_code_with_category{error_code, websocket::websocket_error_category()}; - return std::make_pair(error_code_with_category, error_message); + switch (static_cast(error_code)) { + case WebSocketError::websocket_ok: + case WebSocketError::websocket_going_away: + case WebSocketError::websocket_protocol_error: + case WebSocketError::websocket_unsupported_data: + case WebSocketError::websocket_reserved: + case WebSocketError::websocket_no_status_received: + case WebSocketError::websocket_abnormal_closure: + case WebSocketError::websocket_invalid_payload_data: + case WebSocketError::websocket_policy_violation: + case WebSocketError::websocket_message_too_big: + case WebSocketError::websocket_invalid_extension: + case WebSocketError::websocket_internal_server_error: + case WebSocketError::websocket_tls_handshake_failed: + + case WebSocketError::websocket_unauthorized: + case WebSocketError::websocket_forbidden: + case WebSocketError::websocket_moved_permanently: + case WebSocketError::websocket_client_too_old: + case WebSocketError::websocket_client_too_new: + case WebSocketError::websocket_protocol_mismatch: + break; + default: + error_code = 1008; + } + + return std::make_pair(static_cast(error_code), error_message); } // frame_reader_loop() uses the frame_reader to read and process the incoming @@ -1116,85 +1141,82 @@ class HttpErrorCategory : public std::error_category { } }; -std::string error_string(WebSocketError code) +} // unnamed namespace + +namespace realm::sync::websocket { + +std::ostream& operator<<(std::ostream& os, WebSocketError code) { /// WebSocket error codes - switch (code) { - case WebSocketError::websocket_ok: - return "WebSocket: OK"; - case WebSocketError::websocket_going_away: - return "WebSocket: Going Away"; - case WebSocketError::websocket_protocol_error: - return "WebSocket: Protocol Error"; - case WebSocketError::websocket_unsupported_data: - return "WebSocket: Unsupported Data"; - case WebSocketError::websocket_reserved: - return "WebSocket: Reserved"; - case WebSocketError::websocket_no_status_received: - return "WebSocket: No Status Received"; - case WebSocketError::websocket_abnormal_closure: - return "WebSocket: Abnormal Closure"; - case WebSocketError::websocket_invalid_payload_data: - return "WebSocket: Invalid Payload Data"; - case WebSocketError::websocket_policy_violation: - return "WebSocket: Policy Violation"; - case WebSocketError::websocket_message_too_big: - return "WebSocket: Message Too Big"; - case WebSocketError::websocket_invalid_extension: - return "WebSocket: Invalid Extension"; - case WebSocketError::websocket_internal_server_error: - return "WebSocket: Internal Server Error"; - case WebSocketError::websocket_tls_handshake_failed: - return "WebSocket: TLS Handshake Failed"; - - /// WebSocket Errors - reported by server - case WebSocketError::websocket_unauthorized: - return "WebSocket: Unauthorized"; - case WebSocketError::websocket_forbidden: - return "WebSocket: Forbidden"; - case WebSocketError::websocket_moved_permanently: - return "WebSocket: Moved Permanently"; - case WebSocketError::websocket_client_too_old: - return "WebSocket: Client Too Old"; - case WebSocketError::websocket_client_too_new: - return "WebSocket: Client Too New"; - case WebSocketError::websocket_protocol_mismatch: - return "WebSocket: Protocol Mismatch"; - - case WebSocketError::websocket_resolve_failed: - return "WebSocket: Resolve Failed"; - case WebSocketError::websocket_connection_failed: - return "WebSocket: Connection Failed"; - case WebSocketError::websocket_read_error: - return "WebSocket: Read Error"; - case WebSocketError::websocket_write_error: - return "WebSocket: Write Error"; - case WebSocketError::websocket_retry_error: - return "WebSocket: Retry Error"; - case WebSocketError::websocket_fatal_error: - return "WebSocket: Fatal Error"; - } - return ""; -} + auto str = [&]() -> const char* { + switch (code) { + case WebSocketError::websocket_ok: + return "WebSocket: OK"; + case WebSocketError::websocket_going_away: + return "WebSocket: Going Away"; + case WebSocketError::websocket_protocol_error: + return "WebSocket: Protocol Error"; + case WebSocketError::websocket_unsupported_data: + return "WebSocket: Unsupported Data"; + case WebSocketError::websocket_reserved: + return "WebSocket: Reserved"; + case WebSocketError::websocket_no_status_received: + return "WebSocket: No Status Received"; + case WebSocketError::websocket_abnormal_closure: + return "WebSocket: Abnormal Closure"; + case WebSocketError::websocket_invalid_payload_data: + return "WebSocket: Invalid Payload Data"; + case WebSocketError::websocket_policy_violation: + return "WebSocket: Policy Violation"; + case WebSocketError::websocket_message_too_big: + return "WebSocket: Message Too Big"; + case WebSocketError::websocket_invalid_extension: + return "WebSocket: Invalid Extension"; + case WebSocketError::websocket_internal_server_error: + return "WebSocket: Internal Server Error"; + case WebSocketError::websocket_tls_handshake_failed: + return "WebSocket: TLS Handshake Failed"; + + /// WebSocket Errors - reported by server + case WebSocketError::websocket_unauthorized: + return "WebSocket: Unauthorized"; + case WebSocketError::websocket_forbidden: + return "WebSocket: Forbidden"; + case WebSocketError::websocket_moved_permanently: + return "WebSocket: Moved Permanently"; + case WebSocketError::websocket_client_too_old: + return "WebSocket: Client Too Old"; + case WebSocketError::websocket_client_too_new: + return "WebSocket: Client Too New"; + case WebSocketError::websocket_protocol_mismatch: + return "WebSocket: Protocol Mismatch"; + + case WebSocketError::websocket_resolve_failed: + return "WebSocket: Resolve Failed"; + case WebSocketError::websocket_connection_failed: + return "WebSocket: Connection Failed"; + case WebSocketError::websocket_read_error: + return "WebSocket: Read Error"; + case WebSocketError::websocket_write_error: + return "WebSocket: Write Error"; + case WebSocketError::websocket_retry_error: + return "WebSocket: Retry Error"; + case WebSocketError::websocket_fatal_error: + return "WebSocket: Fatal Error"; + } + return nullptr; + }(); -class WebSocketErrorCategory : public std::error_category { - const char* name() const noexcept final - { - return "realm::sync::websocket::WebSocketError"; + if (str == nullptr) { + os << "WebSocket: Unkhown Error (" << static_cast>(code) << ")"; } - std::string message(int error_code) const final - { - // Converts an error_code to one of the pre-defined status codes in - // https://tools.ietf.org/html/rfc6455#section-7.4.1 - auto msg = error_string(static_cast(error_code)); - if (msg.empty()) - msg = "Unknown error"; - return msg; + else { + os << str; } -}; - -} // unnamed namespace + return os; +} +} // namespace realm::sync::websocket bool websocket::Config::websocket_text_message_received(const char*, size_t) { @@ -1206,7 +1228,7 @@ bool websocket::Config::websocket_binary_message_received(const char*, size_t) return true; } -bool websocket::Config::websocket_close_message_received(std::error_code, StringData) +bool websocket::Config::websocket_close_message_received(WebSocketError, std::string_view) { return true; } @@ -1314,17 +1336,6 @@ util::Optional websocket::make_http_response(const HTTPRequest& re return do_make_http_response(request, sec_websocket_protocol, ec); } -const std::error_category& websocket::websocket_error_category() noexcept -{ - static const WebSocketErrorCategory category = {}; - return category; -} - -std::error_code websocket::make_error_code(WebSocketError error) noexcept -{ - return std::error_code{int(error), websocket_error_category()}; -} - const std::error_category& websocket::http_error_category() noexcept { static const HttpErrorCategory category = {}; @@ -1335,25 +1346,3 @@ std::error_code websocket::make_error_code(HttpError error_code) noexcept { return std::error_code{int(error_code), http_error_category()}; } - -ErrorCodes::Error websocket::get_simplified_websocket_error(WebSocketError error) -{ - if (error == sync::websocket::WebSocketError::websocket_resolve_failed) { - return ErrorCodes::WebSocketResolveFailedError; - } - else if (error == sync::websocket::WebSocketError::websocket_connection_failed || - error == sync::websocket::WebSocketError::websocket_unauthorized || - error == sync::websocket::WebSocketError::websocket_forbidden || - error == sync::websocket::WebSocketError::websocket_moved_permanently || - error == sync::websocket::WebSocketError::websocket_client_too_old || - error == sync::websocket::WebSocketError::websocket_client_too_new || - error == sync::websocket::WebSocketError::websocket_protocol_mismatch || - error == sync::websocket::WebSocketError::websocket_read_error || - error == sync::websocket::WebSocketError::websocket_write_error || - error == sync::websocket::WebSocketError::websocket_retry_error || - error == sync::websocket::WebSocketError::websocket_fatal_error) { - return ErrorCodes::WebSocketConnectionClosedClientError; - } - - return ErrorCodes::WebSocketConnectionClosedServerError; -} diff --git a/src/realm/sync/network/websocket.hpp b/src/realm/sync/network/websocket.hpp index 1cb518410bf..0c5e21e7788 100644 --- a/src/realm/sync/network/websocket.hpp +++ b/src/realm/sync/network/websocket.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -76,7 +77,7 @@ class Config { /// websocket object is destroyed during execution of the function. virtual bool websocket_text_message_received(const char* data, size_t size); virtual bool websocket_binary_message_received(const char* data, size_t size); - virtual bool websocket_close_message_received(std::error_code error_code, StringData message); + virtual bool websocket_close_message_received(WebSocketError code, std::string_view message); virtual bool websocket_ping_message_received(const char* data, size_t size); virtual bool websocket_pong_message_received(const char* data, size_t size); //@} @@ -221,43 +222,6 @@ const std::error_category& http_error_category() noexcept; std::error_code make_error_code(HttpError) noexcept; -enum class WebSocketError { - websocket_ok = RLM_ERR_WEBSOCKET_OK, - websocket_going_away = RLM_ERR_WEBSOCKET_GOINGAWAY, - websocket_protocol_error = RLM_ERR_WEBSOCKET_PROTOCOLERROR, - websocket_unsupported_data = RLM_ERR_WEBSOCKET_UNSUPPORTEDDATA, - websocket_reserved = RLM_ERR_WEBSOCKET_RESERVED, - websocket_no_status_received = RLM_ERR_WEBSOCKET_NOSTATUSRECEIVED, - websocket_abnormal_closure = RLM_ERR_WEBSOCKET_ABNORMALCLOSURE, - websocket_invalid_payload_data = RLM_ERR_WEBSOCKET_INVALIDPAYLOADDATA, - websocket_policy_violation = RLM_ERR_WEBSOCKET_POLICYVIOLATION, - websocket_message_too_big = RLM_ERR_WEBSOCKET_MESSAGETOOBIG, - websocket_invalid_extension = RLM_ERR_WEBSOCKET_INAVALIDEXTENSION, - websocket_internal_server_error = RLM_ERR_WEBSOCKET_INTERNALSERVERERROR, - websocket_tls_handshake_failed = RLM_ERR_WEBSOCKET_TLSHANDSHAKEFAILED, // Used by default WebSocket - - // WebSocket Errors - reported by server - websocket_unauthorized = RLM_ERR_WEBSOCKET_UNAUTHORIZED, - websocket_forbidden = RLM_ERR_WEBSOCKET_FORBIDDEN, - websocket_moved_permanently = RLM_ERR_WEBSOCKET_MOVEDPERMANENTLY, - websocket_client_too_old = RLM_ERR_WEBSOCKET_CLIENT_TOO_OLD, - websocket_client_too_new = RLM_ERR_WEBSOCKET_CLIENT_TOO_NEW, - websocket_protocol_mismatch = RLM_ERR_WEBSOCKET_PROTOCOL_MISMATCH, - - websocket_resolve_failed = RLM_ERR_WEBSOCKET_RESOLVE_FAILED, - websocket_connection_failed = RLM_ERR_WEBSOCKET_CONNECTION_FAILED, - websocket_read_error = RLM_ERR_WEBSOCKET_READ_ERROR, - websocket_write_error = RLM_ERR_WEBSOCKET_WRITE_ERROR, - websocket_retry_error = RLM_ERR_WEBSOCKET_RETRY_ERROR, - websocket_fatal_error = RLM_ERR_WEBSOCKET_FATAL_ERROR, -}; - -const std::error_category& websocket_error_category() noexcept; - -std::error_code make_error_code(WebSocketError) noexcept; - -ErrorCodes::Error get_simplified_websocket_error(WebSocketError); - } // namespace realm::sync::websocket namespace std { @@ -267,9 +231,4 @@ struct is_error_code_enum { static const bool value = true; }; -template <> -struct is_error_code_enum { - static const bool value = true; -}; - } // namespace std diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp index c9b00ed8946..c70b88dac5e 100644 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ b/src/realm/sync/noinst/client_impl_base.cpp @@ -499,30 +499,31 @@ void Connection::websocket_error_handler() m_websocket_error_received = true; } -bool Connection::websocket_closed_handler(bool was_clean, Status status) +bool Connection::websocket_closed_handler(bool was_clean, WebSocketError error_code, std::string_view msg) { if (m_force_closed) { logger.debug("Received websocket close message after connection was force closed"); return false; } - logger.info("Closing the websocket with status='%1', was_clean='%2'", status, was_clean); - auto error_code = status.get_std_error_code(); + logger.info("Closing the websocket with error code=%1, message=%2, was_clean='%2'", static_cast(error_code), + msg, was_clean); - switch (static_cast(error_code.value())) { + switch (error_code) { case WebSocketError::websocket_ok: break; case WebSocketError::websocket_resolve_failed: [[fallthrough]]; case WebSocketError::websocket_connection_failed: { - constexpr bool try_again = true; - involuntary_disconnect(SessionErrorInfo{std::move(status), try_again}, - ConnectionTerminationReason::connect_operation_failed); // Throws + SessionErrorInfo error_info( + {ErrorCodes::SyncConnectFailed, util::format("Failed to connect to sync: %1", msg)}, IsFatal{false}); + involuntary_disconnect(std::move(error_info), ConnectionTerminationReason::connect_operation_failed); break; } case WebSocketError::websocket_read_error: [[fallthrough]]; case WebSocketError::websocket_write_error: { - read_or_write_error(error_code, status.reason()); // Throws + close_due_to_transient_error({ErrorCodes::ConnectionClosed, msg}, + ConnectionTerminationReason::read_or_write_error); break; } case WebSocketError::websocket_going_away: @@ -540,61 +541,67 @@ bool Connection::websocket_closed_handler(bool was_clean, Status status) case WebSocketError::websocket_no_status_received: [[fallthrough]]; case WebSocketError::websocket_invalid_extension: { - constexpr bool try_again = true; - SessionErrorInfo error_info{std::move(status), try_again}; - involuntary_disconnect(std::move(error_info), - ConnectionTerminationReason::websocket_protocol_violation); // Throws + close_due_to_client_side_error({ErrorCodes::SyncProtocolInvariantFailed, msg}, IsFatal{false}, + ConnectionTerminationReason::websocket_protocol_violation); // Throws break; } case WebSocketError::websocket_message_too_big: { - constexpr bool try_again = true; - auto message = - util::format("Sync websocket closed because the server received a message that was too large: %1", - status.reason()); - SessionErrorInfo error_info(Status(ErrorCodes::LimitExceeded, std::move(message)), try_again); + auto message = util::format( + "Sync websocket closed because the server received a message that was too large: %1", msg); + SessionErrorInfo error_info(Status(ErrorCodes::LimitExceeded, std::move(message)), IsFatal{false}); error_info.server_requests_action = ProtocolErrorInfo::Action::ClientReset; involuntary_disconnect(std::move(error_info), ConnectionTerminationReason::websocket_protocol_violation); // Throws break; } case WebSocketError::websocket_tls_handshake_failed: { - close_due_to_client_side_error(Status(ErrorCodes::TlsHandshakeFailed, status.reason()), IsFatal{false}, + close_due_to_client_side_error(Status(ErrorCodes::TlsHandshakeFailed, msg), IsFatal{false}, ConnectionTerminationReason::ssl_certificate_rejected); // Throws break; } - case WebSocketError::websocket_client_too_old: { - close_due_to_client_side_error(std::move(status), IsFatal{true}, + case WebSocketError::websocket_client_too_old: + [[fallthrough]]; + case WebSocketError::websocket_client_too_new: + [[fallthrough]]; + case WebSocketError::websocket_protocol_mismatch: { + close_due_to_client_side_error({ErrorCodes::SyncProtocolNegotiationFailed, msg}, IsFatal{true}, ConnectionTerminationReason::http_response_says_fatal_error); // Throws break; } - case WebSocketError::websocket_client_too_new: { - close_due_to_client_side_error(std::move(status), IsFatal{true}, - ConnectionTerminationReason::http_response_says_fatal_error); // Throws + case WebSocketError::websocket_fatal_error: { + involuntary_disconnect(SessionErrorInfo({ErrorCodes::ConnectionClosed, msg}, IsFatal{true}), + ConnectionTerminationReason::http_response_says_fatal_error); break; } - case WebSocketError::websocket_protocol_mismatch: { - close_due_to_client_side_error(std::move(status), IsFatal{true}, - ConnectionTerminationReason::http_response_says_fatal_error); // Throws + case WebSocketError::websocket_forbidden: { + involuntary_disconnect(SessionErrorInfo({ErrorCodes::AuthError, msg}, IsFatal{true}), + ConnectionTerminationReason::http_response_says_fatal_error); break; } - case WebSocketError::websocket_fatal_error: - [[fallthrough]]; - case WebSocketError::websocket_forbidden: { - close_due_to_client_side_error(std::move(status), IsFatal{true}, - ConnectionTerminationReason::http_response_says_fatal_error); // Throws + case WebSocketError::websocket_unauthorized: { + SessionErrorInfo error_info( + {ErrorCodes::AuthError, + util::format("Websocket was closed because of an authentication issue: %1", msg)}, + IsFatal{false}); + error_info.server_requests_action = ProtocolErrorInfo::Action::RefreshLocation; + involuntary_disconnect(std::move(error_info), + ConnectionTerminationReason::http_response_says_nonfatal_error); break; } - case WebSocketError::websocket_unauthorized: - [[fallthrough]]; case WebSocketError::websocket_moved_permanently: [[fallthrough]]; + case WebSocketError::websocket_abnormal_closure: { + SessionErrorInfo error_info({ErrorCodes::ConnectionClosed, msg}, IsFatal{false}); + error_info.server_requests_action = ProtocolErrorInfo::Action::RefreshUser; + involuntary_disconnect(std::move(error_info), + ConnectionTerminationReason::http_response_says_nonfatal_error); + break; + } case WebSocketError::websocket_internal_server_error: [[fallthrough]]; - case WebSocketError::websocket_abnormal_closure: - [[fallthrough]]; case WebSocketError::websocket_retry_error: { - close_due_to_client_side_error(error_code, status.reason(), IsFatal{false}, - ConnectionTerminationReason::http_response_says_nonfatal_error); // Throws + involuntary_disconnect(SessionErrorInfo({ErrorCodes::ConnectionClosed, msg}, IsFatal{false}), + ConnectionTerminationReason::http_response_says_nonfatal_error); break; } } @@ -695,13 +702,13 @@ struct Connection::WebSocketObserverShim : public sync::WebSocketObserver { return conn->websocket_binary_message_received(data); } - bool websocket_closed_handler(bool was_clean, Status status) override + bool websocket_closed_handler(bool was_clean, WebSocketError error_code, std::string_view msg) override { if (sentinel->destroyed) { return true; } - return conn->websocket_closed_handler(was_clean, std::move(status)); + return conn->websocket_closed_handler(was_clean, error_code, msg); } }; @@ -782,8 +789,7 @@ void Connection::handle_connect_wait(Status status) REALM_ASSERT_EX(m_state == ConnectionState::connecting, m_state); logger.info("Connect timeout"); // Throws - constexpr bool try_again = true; - involuntary_disconnect(SessionErrorInfo{Status{ErrorCodes::SyncConnectFailed, status.reason()}, try_again}, + involuntary_disconnect(SessionErrorInfo{Status{ErrorCodes::SyncConnectFailed, status.reason()}, IsFatal{false}}, ConnectionTerminationReason::sync_connect_timeout); // Throws } @@ -1117,9 +1123,8 @@ void Connection::close_due_to_client_side_error(std::error_code ec, std::optiona void Connection::close_due_to_client_side_error(Status status, IsFatal is_fatal, ConnectionTerminationReason reason) { logger.info("Connection closed due to error: %1", status); // Throws - const bool try_again = !is_fatal; - involuntary_disconnect(SessionErrorInfo{std::move(status), try_again}, reason); // Throw + involuntary_disconnect(SessionErrorInfo{std::move(status), is_fatal}, reason); // Throw } void Connection::close_due_to_transient_error(Status status, ConnectionTerminationReason reason) @@ -1138,8 +1143,8 @@ void Connection::close_due_to_server_side_error(ProtocolError error_code, const logger.info("Connection closed due to error reported by server: %1 (%2)", info.message, int(error_code)); // Throws - const auto reason = info.try_again ? ConnectionTerminationReason::server_said_try_again_later - : ConnectionTerminationReason::server_said_do_not_reconnect; + const auto reason = info.is_fatal ? ConnectionTerminationReason::server_said_do_not_reconnect + : ConnectionTerminationReason::server_said_try_again_later; involuntary_disconnect(SessionErrorInfo{info, protocol_error_to_status(error_code, info.message)}, reason); // Throws } @@ -1211,9 +1216,9 @@ void Connection::receive_pong(milliseconds_type timestamp) if (REALM_UNLIKELY(timestamp != m_last_ping_sent_at)) { close_due_to_protocol_error( - {ErrorCodes::SyncProtocolInvariantFailed, - util::format("Received PONG message with an invalid timestamp (expected %1, received %2", - m_last_ping_sent_at, timestamp)}); // Throws + {ErrorCodes::SyncProtocolInvariantFailed, util::format("Received PONG message with an invalid timestamp " + "(expected %1, received %2", + m_last_ping_sent_at, timestamp)}); // Throws return; } @@ -1253,10 +1258,10 @@ Session* Connection::find_and_validate_session(session_ident_type session_ident, // Check the history to see if the message received was for a previous session if (auto it = m_session_history.find(session_ident); it == m_session_history.end()) { logger.error("Bad session identifier in %1 message, session_ident = %2", message, session_ident); - close_due_to_protocol_error( - {ErrorCodes::SyncProtocolInvariantFailed, - util::format("Received message %1 for session iden %2 when that session never existed", message, - session_ident)}); + close_due_to_protocol_error({ErrorCodes::SyncProtocolInvariantFailed, + util::format("Received message %1 for session iden %2 when that " + "session never existed", + message, session_ident)}); } else { logger.error("Received %1 message for closed session, session_ident = %2", message, @@ -1284,8 +1289,9 @@ void Connection::receive_error_message(const ProtocolErrorInfo& info, session_id return; } - logger.info("Received: ERROR \"%1\" (error_code=%2, try_again=%3, session_ident=%4, error_action=%5)", - info.message, info.raw_error_code, info.try_again, session_ident, + logger.info("Received: ERROR \"%1\" (error_code=%2, is_fatal=%3, " + "session_ident=%4, error_action=%5)", + info.message, info.raw_error_code, info.is_fatal, session_ident, info.server_requests_action); // Throws bool known_error_code = bool(get_protocol_error_message(info.raw_error_code)); @@ -1295,10 +1301,10 @@ void Connection::receive_error_message(const ProtocolErrorInfo& info, session_id close_due_to_server_side_error(error_code, info); // Throws return; } - close_due_to_protocol_error( - {ErrorCodes::SyncProtocolInvariantFailed, - util::format("Received ERROR message with a non-connection-level error code %1 without a session ident", - info.raw_error_code)}); + close_due_to_protocol_error({ErrorCodes::SyncProtocolInvariantFailed, + util::format("Received ERROR message with a non-connection-level " + "error code %1 without a session ident", + info.raw_error_code)}); } else { close_due_to_protocol_error( @@ -1515,7 +1521,8 @@ void Session::integrate_changesets(ClientReplication& repl, const SyncProgress& "received empty download message that was not the last in batch", ProtocolError::bad_progress); } - history.set_sync_progress(progress, &downloadable_bytes, version_info); // Throws + history.set_sync_progress(progress, &downloadable_bytes, + version_info); // Throws return; } @@ -1533,11 +1540,13 @@ void Session::integrate_changesets(ClientReplication& repl, const SyncProgress& } else { logger.debug("%2 remote changesets integrated, producing client version %1", - version_info.sync_version.version, received_changesets.size()); // Throws + version_info.sync_version.version, + received_changesets.size()); // Throws } for (const auto& pending_error : pending_compensating_write_errors) { - logger.info("Reporting compensating write for client version %1 in server version %2: %3", + logger.info("Reporting compensating write for client version %1 in server " + "version %2: %3", pending_error.compensating_write_rejected_client_version, pending_error.compensating_write_server_version, pending_error.message); try { @@ -1563,9 +1572,8 @@ void Session::on_integration_failure(const IntegrationException& error) m_client_error = util::make_optional(error); m_error_to_send = true; - constexpr bool try_again = true; // Surface the error to the user otherwise is lost. - on_connection_state_changed(m_conn.get_state(), SessionErrorInfo{error.to_status(), try_again}); + on_connection_state_changed(m_conn.get_state(), SessionErrorInfo{error.to_status(), IsFatal{false}}); // Since the deactivation process has not been initiated, the UNBIND // message cannot have been sent unless an ERROR message was received. @@ -1595,7 +1603,8 @@ void Session::on_changesets_integrated(version_type client_version, const SyncPr do_recognize_sync_version(client_version); // Allows upload process to resume check_for_download_completion(); // Throws - // If the client migrated from PBS to FLX, create subscriptions when new tables are received from server. + // If the client migrated from PBS to FLX, create subscriptions when new tables + // are received from server. if (auto migration_store = get_migration_store(); migration_store && m_is_flx_sync_session) { auto& flx_subscription_store = *get_flx_subscription_store(); get_migration_store()->create_subscriptions(flx_subscription_store); @@ -1612,7 +1621,8 @@ void Session::on_changesets_integrated(version_type client_version, const SyncPr Session::~Session() { - // REALM_ASSERT_EX(m_state == Unactivated || m_state == Deactivated, m_state); + // REALM_ASSERT_EX(m_state == Unactivated || m_state == Deactivated, + // m_state); } @@ -1633,13 +1643,15 @@ void Session::activate() bool has_pending_client_reset = false; if (REALM_LIKELY(!get_client().is_dry_run())) { - // The reason we need a mutable reference from get_client_reset_config() is because we - // don't want the session to keep a strong reference to the client_reset_config->fresh_copy - // DB. If it did, then the fresh DB would stay alive for the duration of this sync session - // and we want to clean it up once the reset is finished. Additionally, the fresh copy will - // be set to a new copy on every reset so there is no reason to keep a reference to it. - // The modification to the client reset config happens via std::move(client_reset_config->fresh_copy). - // If the client reset config were a `const &` then this std::move would create another strong + // The reason we need a mutable reference from get_client_reset_config() is + // because we don't want the session to keep a strong reference to the + // client_reset_config->fresh_copy DB. If it did, then the fresh DB would + // stay alive for the duration of this sync session and we want to clean it + // up once the reset is finished. Additionally, the fresh copy will be set + // to a new copy on every reset so there is no reason to keep a reference + // to it. The modification to the client reset config happens via + // std::move(client_reset_config->fresh_copy). If the client reset config + // were a `const &` then this std::move would create another strong // reference which we don't want to happen. util::Optional& client_reset_config = get_client_reset_config(); @@ -1671,8 +1683,10 @@ void Session::activate() m_download_progress = m_progress.download; REALM_ASSERT_3(m_last_version_available, >=, m_progress.upload.client_version); - logger.debug("last_version_available = %1", m_last_version_available); // Throws - logger.debug("progress_server_version = %1", m_progress.download.server_version); // Throws + logger.debug("last_version_available = %1", + m_last_version_available); // Throws + logger.debug("progress_server_version = %1", + m_progress.download.server_version); // Throws logger.debug("progress_client_version = %1", m_progress.download.last_integrated_client_version); // Throws @@ -1792,7 +1806,8 @@ void Session::send_message() if (m_error_to_send) return send_json_error_message(); // Throws - // Stop sending upload, mark and query messages when the client detects an error. + // Stop sending upload, mark and query messages when the client detects an + // error. if (m_client_error) { return; } @@ -1815,7 +1830,8 @@ void Session::send_message() return true; } - // Do not allow upload if the last query sent is the sentinel one used by the migration store. + // Do not allow upload if the last query sent is the sentinel one used by + // the migration store. return m_last_sent_flx_query_version != *sentinel_query_version; }; @@ -1878,19 +1894,23 @@ void Session::send_bind_message() if (!bind_json_data.empty()) { json_data_dump = bind_json_data.dump(); } - logger.debug( - "Sending: BIND(session_ident=%1, need_client_file_ident=%2, is_subserver=%3, json_data=\"%4\")", - session_ident, need_client_file_ident, is_subserver, json_data_dump); + logger.debug("Sending: BIND(session_ident=%1, " + "need_client_file_ident=%2, is_subserver=%3, " + "json_data=\"%4\")", + session_ident, need_client_file_ident, is_subserver, json_data_dump); } protocol.make_flx_bind_message(protocol_version, out, session_ident, bind_json_data, empty_access_token, - need_client_file_ident, is_subserver); // Throws + need_client_file_ident, + is_subserver); // Throws } else { std::string server_path = get_virt_path(); - logger.debug("Sending: BIND(session_ident=%1, need_client_file_ident=%2, is_subserver=%3, server_path=%4)", + logger.debug("Sending: BIND(session_ident=%1, need_client_file_ident=%2, " + "is_subserver=%3, server_path=%4)", session_ident, need_client_file_ident, is_subserver, server_path); protocol.make_pbs_bind_message(protocol_version, out, session_ident, server_path, empty_access_token, - need_client_file_ident, is_subserver); // Throws + need_client_file_ident, + is_subserver); // Throws } m_conn.initiate_write_message(out, this); // Throws @@ -1919,24 +1939,29 @@ void Session::send_ident_message() const auto active_query_set = get_flx_subscription_store()->get_active(); const auto active_query_body = active_query_set.to_ext_json(); logger.debug("Sending: IDENT(client_file_ident=%1, client_file_ident_salt=%2, " - "scan_server_version=%3, scan_client_version=%4, latest_server_version=%5, " - "latest_server_version_salt=%6, query_version=%7, query_size=%8, query=\"%9\")", + "scan_server_version=%3, scan_client_version=%4, " + "latest_server_version=%5, " + "latest_server_version_salt=%6, query_version=%7, query_size=%8, " + "query=\"%9\")", m_client_file_ident.ident, m_client_file_ident.salt, m_progress.download.server_version, m_progress.download.last_integrated_client_version, m_progress.latest_server_version.version, m_progress.latest_server_version.salt, active_query_set.version(), active_query_body.size(), active_query_body); // Throws protocol.make_flx_ident_message(out, session_ident, m_client_file_ident, m_progress, - active_query_set.version(), active_query_body); // Throws + active_query_set.version(), + active_query_body); // Throws m_last_sent_flx_query_version = active_query_set.version(); } else { logger.debug("Sending: IDENT(client_file_ident=%1, client_file_ident_salt=%2, " - "scan_server_version=%3, scan_client_version=%4, latest_server_version=%5, " + "scan_server_version=%3, scan_client_version=%4, " + "latest_server_version=%5, " "latest_server_version_salt=%6)", m_client_file_ident.ident, m_client_file_ident.salt, m_progress.download.server_version, m_progress.download.last_integrated_client_version, m_progress.latest_server_version.version, - m_progress.latest_server_version.salt); // Throws - protocol.make_pbs_ident_message(out, session_ident, m_client_file_ident, m_progress); // Throws + m_progress.latest_server_version.salt); // Throws + protocol.make_pbs_ident_message(out, session_ident, m_client_file_ident, + m_progress); // Throws } m_conn.initiate_write_message(out, this); // Throws @@ -1961,7 +1986,8 @@ void Session::send_query_change_message() auto sub_store = get_flx_subscription_store(); auto latest_sub_set = sub_store->get_by_version(m_pending_flx_sub_set->query_version); auto latest_queries = latest_sub_set.to_ext_json(); - logger.debug("Sending: QUERY(query_version=%1, query_size=%2, query=\"%3\", snapshot_version=%4)", + logger.debug("Sending: QUERY(query_version=%1, query_size=%2, query=\"%3\", " + "snapshot_version=%4)", latest_sub_set.version(), latest_queries.size(), latest_queries, latest_sub_set.snapshot_version()); OutputBuffer& out = m_conn.get_output_buffer(); @@ -1992,7 +2018,8 @@ void Session::send_upload_message() m_last_sent_flx_query_version, m_upload_progress.client_version); } if (m_pending_flx_sub_set && m_pending_flx_sub_set->snapshot_version < m_upload_target_version) { - logger.trace("Limiting UPLOAD message up to version %1 to send QUERY version %2", + logger.trace("Limiting UPLOAD message up to version %1 to send QUERY " + "version %2", m_pending_flx_sub_set->snapshot_version, m_pending_flx_sub_set->query_version); target_upload_version = m_pending_flx_sub_set->snapshot_version; } @@ -2028,7 +2055,8 @@ void Session::send_upload_message() logger.debug("Fetching changeset for upload (client_version=%1, server_version=%2, " "changeset_size=%3, origin_timestamp=%4, origin_file_ident=%5)", uc.progress.client_version, uc.progress.last_integrated_server_version, uc.changeset.size(), - uc.origin_timestamp, uc.origin_file_ident); // Throws + uc.origin_timestamp, + uc.origin_file_ident); // Throws if (logger.would_log(util::Logger::Level::trace)) { BinaryData changeset_data = uc.changeset.get_first_chunk(); if (changeset_data.size() < 1024) { @@ -2234,11 +2262,11 @@ Status Session::receive_ident_message(SaltedFileIdent client_file_ident) return false; } - // ClientResetOperation::finalize() will return true only if the operation actually did - // a client reset. It may choose not to do a reset if the local Realm does not exist - // at this point (in that case there is nothing to reset). But in any case, we must - // clean up m_client_reset_operation at this point as sync should be able to continue from - // this point forward. + // ClientResetOperation::finalize() will return true only if the operation + // actually did a client reset. It may choose not to do a reset if the + // local Realm does not exist at this point (in that case there is nothing + // to reset). But in any case, we must clean up m_client_reset_operation at + // this point as sync should be able to continue from this point forward. auto client_reset_operation = std::move(m_client_reset_operation); util::UniqueFunction on_flx_subscription_complete = [this](int64_t version) { this->on_flx_sync_version_complete(version); @@ -2251,7 +2279,8 @@ Status Session::receive_ident_message(SaltedFileIdent client_file_ident) realm::VersionID client_reset_new_version = client_reset_operation->get_client_reset_new_version(); // 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 SaltedFileIdent client_file_ident; bool has_pending_client_reset = false; @@ -2264,14 +2293,16 @@ Status Session::receive_ident_message(SaltedFileIdent client_file_ident) REALM_ASSERT_EX(m_progress.upload.client_version == 0, m_progress.upload.client_version); REALM_ASSERT_EX(m_progress.upload.last_integrated_server_version == 0, m_progress.upload.last_integrated_server_version); - logger.trace("last_version_available = %1", m_last_version_available); // Throws + logger.trace("last_version_available = %1", + m_last_version_available); // Throws m_upload_target_version = m_last_version_available; m_upload_progress = m_progress.upload; m_download_progress = m_progress.download; - // In recovery mode, there may be new changesets to upload and nothing left to download. - // In FLX DiscardLocal mode, there may be new commits due to subscription handling. - // For both, we want to allow uploads again without needing external changes to download first. + // In recovery mode, there may be new changesets to upload and nothing left + // to download. In FLX DiscardLocal mode, there may be new commits due to + // subscription handling. For both, we want to allow uploads again without + // needing external changes to download first. m_allow_upload = true; REALM_ASSERT_EX(m_last_version_selected_for_upload == 0, m_last_version_selected_for_upload); @@ -2281,7 +2312,8 @@ Status Session::receive_ident_message(SaltedFileIdent client_file_ident) handle_pending_client_reset_acknowledgement(); } - // If a migration or rollback is in progress, mark it complete when client reset is completed. + // If a migration or rollback is in progress, mark it complete when client + // reset is completed. if (auto migration_store = get_migration_store()) { migration_store->complete_migration_or_rollback(); } @@ -2302,7 +2334,8 @@ Status Session::receive_ident_message(SaltedFileIdent client_file_ident) return Status::OK(); } if (!did_client_reset) { - repl.get_history().set_client_file_ident(client_file_ident, m_fix_up_object_ids); // Throws + repl.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; @@ -2327,9 +2360,11 @@ Status Session::receive_download_message(const SyncProgress& progress, std::uint batch_state = DownloadBatchState::SteadyState; } - logger.debug("Received: DOWNLOAD(download_server_version=%1, download_client_version=%2, " + logger.debug("Received: DOWNLOAD(download_server_version=%1, " + "download_client_version=%2, " "latest_server_version=%3, latest_server_version_salt=%4, " - "upload_client_version=%5, upload_server_version=%6, downloadable_bytes=%7, " + "upload_client_version=%5, upload_server_version=%6, " + "downloadable_bytes=%7, " "last_in_batch=%8, query_version=%9, num_changesets=%10, ...)", progress.download.server_version, progress.download.last_integrated_client_version, progress.latest_server_version.version, progress.latest_server_version.salt, @@ -2337,10 +2372,11 @@ Status Session::receive_download_message(const SyncProgress& progress, std::uint batch_state != DownloadBatchState::MoreToCome, query_version, received_changesets.size()); // Throws - // Ignore download messages when the client detects an error. This is to prevent transforming the same bad - // changeset over and over again. + // Ignore download messages when the client detects an error. This is to + // prevent transforming the same bad changeset over and over again. if (m_client_error) { - logger.debug("Ignoring download message because the client detected an integration error"); + logger.debug("Ignoring download message because the client detected an " + "integration error"); return Status::OK(); } @@ -2356,13 +2392,15 @@ Status Session::receive_download_message(const SyncProgress& progress, std::uint version_type server_version = m_progress.download.server_version; version_type last_integrated_client_version = m_progress.download.last_integrated_client_version; for (const Transformer::RemoteChangeset& changeset : received_changesets) { - // Check that per-changeset server version is strictly increasing, except in FLX sync where the server - // version must be increasing, but can stay the same during bootstraps. + // Check that per-changeset server version is strictly increasing, except + // in FLX sync where the server version must be increasing, but can stay + // the same during bootstraps. bool good_server_version = m_is_flx_sync_session ? (changeset.remote_version >= server_version) : (changeset.remote_version > server_version); if (!good_server_version) { return {ErrorCodes::SyncProtocolInvariantFailed, - util::format("Bad server version in changeset header (DOWNLOAD) (%1, %2, %3)", + util::format("Bad server version in changeset header " + "(DOWNLOAD) (%1, %2, %3)", changeset.remote_version, server_version, progress.download.server_version)}; } server_version = changeset.remote_version; @@ -2373,7 +2411,8 @@ Status Session::receive_download_message(const SyncProgress& progress, std::uint changeset.last_integrated_local_version <= progress.download.last_integrated_client_version); if (!good_client_version) { return {ErrorCodes::SyncProtocolInvariantFailed, - util::format("Bad last integrated client version in changeset header (DOWNLOAD) " + util::format("Bad last integrated client version in changeset " + "header (DOWNLOAD) " "(%1, %2, %3)", changeset.last_integrated_local_version, last_integrated_client_version, progress.download.last_integrated_client_version)}; @@ -2402,7 +2441,8 @@ Status Session::receive_download_message(const SyncProgress& progress, std::uint return Status::OK(); } - initiate_integrate_changesets(downloadable_bytes, batch_state, progress, received_changesets); // Throws + initiate_integrate_changesets(downloadable_bytes, batch_state, progress, + received_changesets); // Throws hook_action = call_debug_hook(SyncClientHookEvent::DownloadMessageIntegrated, progress, query_version, batch_state, received_changesets.size()); @@ -2411,8 +2451,8 @@ Status Session::receive_download_message(const SyncProgress& progress, std::uint } REALM_ASSERT_EX(hook_action == SyncClientHookAction::NoAction, hook_action); - // When we receive a DOWNLOAD message successfully, we can clear the backoff timer value used to reconnect - // after a retryable session error. + // When we receive a DOWNLOAD message successfully, we can clear the backoff + // timer value used to reconnect after a retryable session error. clear_resumption_delay_state(); return Status::OK(); } @@ -2434,11 +2474,11 @@ Status Session::receive_mark_message(request_ident_type request_ident) bool good_request_ident = (request_ident <= m_last_download_mark_sent && request_ident > m_last_download_mark_received); if (REALM_UNLIKELY(!good_request_ident)) { - return { - ErrorCodes::SyncProtocolInvariantFailed, - util::format( - "Received MARK message with invalid request identifer (last mark sent: %1 last mark received: %2)", - m_last_download_mark_sent, m_last_download_mark_received)}; + return {ErrorCodes::SyncProtocolInvariantFailed, + util::format("Received MARK message with invalid request " + "identifer (last mark sent: %1 last mark " + "received: %2)", + m_last_download_mark_sent, m_last_download_mark_received)}; } m_server_version_at_last_download_mark = m_progress.download.server_version; @@ -2473,7 +2513,7 @@ Status Session::receive_unbound_message() // The deactivation process completes when the unbinding process // completes. complete_deactivation(); // Throws - // Life cycle state is now Deactivated + // Life cycle state is now Deactivated } return Status::OK(); // Success @@ -2496,8 +2536,9 @@ Status Session::receive_query_error_message(int error_code, std::string_view mes // deactivated upon return. Status Session::receive_error_message(const ProtocolErrorInfo& info) { - logger.info("Received: ERROR \"%1\" (error_code=%2, try_again=%3, error_action=%4)", info.message, - info.raw_error_code, info.try_again, info.server_requests_action); // Throws + logger.info("Received: ERROR \"%1\" (error_code=%2, is_fatal=%3, error_action=%4)", info.message, + info.raw_error_code, info.is_fatal, + info.server_requests_action); // Throws bool legal_at_this_time = (m_bind_message_sent && !m_error_message_received && !m_unbound_message_received); if (REALM_UNLIKELY(!legal_at_this_time)) { @@ -2511,13 +2552,13 @@ Status Session::receive_error_message(const ProtocolErrorInfo& info) } ProtocolError error_code = ProtocolError(info.raw_error_code); if (REALM_UNLIKELY(!is_session_level_error(error_code))) { - return {ErrorCodes::SyncProtocolInvariantFailed, - util::format("Received ERROR message for session with non-session-level error code %1", - info.raw_error_code)}; + return {ErrorCodes::SyncProtocolInvariantFailed, util::format("Received ERROR message for session with " + "non-session-level error code %1", + info.raw_error_code)}; } - // Can't process debug hook actions once the Session is undergoing deactivation, since - // the SessionWrapper may not be available + // Can't process debug hook actions once the Session is undergoing + // deactivation, since the SessionWrapper may not be available if (m_state == Active) { auto debug_action = call_debug_hook(SyncClientHookEvent::ErrorMessageReceived, info); if (debug_action == SyncClientHookAction::EarlyReturn) { @@ -2525,11 +2566,12 @@ Status Session::receive_error_message(const ProtocolErrorInfo& info) } } - // For compensating write errors, we need to defer raising them to the SDK until after the server version - // containing the compensating write has appeared in a download message. + // For compensating write errors, we need to defer raising them to the SDK + // until after the server version containing the compensating write has + // appeared in a download message. if (error_code == ProtocolError::compensating_write) { - // If the client is not active, the compensating writes will not be processed now, but will be - // sent again the next time the client connects + // If the client is not active, the compensating writes will not be + // processed now, but will be sent again the next time the client connects if (m_state == Active) { m_pending_compensating_write_errors.push_back(info); } @@ -2551,15 +2593,16 @@ void Session::suspend(const SessionErrorInfo& info) // Detect completion of the unbinding process if (m_unbind_message_send_complete && m_error_message_received) { - // The fact that the UNBIND message has been sent, but we are not being suspended because - // we received an ERROR message implies that the deactivation process must - // have been initiated, so this session must be in the Deactivating state. + // The fact that the UNBIND message has been sent, but we are not being + // suspended because we received an ERROR message implies that the + // deactivation process must have been initiated, so this session must be + // in the Deactivating state. REALM_ASSERT_EX(m_state == Deactivating, m_state); // The deactivation process completes when the unbinding process // completes. complete_deactivation(); // Throws - // Life cycle state is now Deactivated + // Life cycle state is now Deactivated } // Notify the application of the suspension of the session if the session is @@ -2569,7 +2612,7 @@ void Session::suspend(const SessionErrorInfo& info) on_suspended(info); // Throws } - if (info.try_again) { + if (!info.is_fatal) { begin_resumption_delay(info); } @@ -2604,9 +2647,11 @@ void Session::begin_resumption_delay(const ProtocolErrorInfo& error_info) error_info.resumption_delay_interval); auto try_again_interval = m_try_again_delay_info.delay_interval(); if (ProtocolError(error_info.raw_error_code) == ProtocolError::session_closed) { - // FIXME With compensating writes the server sends this error after completing a bootstrap. Doing the - // normal backoff behavior would result in waiting up to 5 minutes in between each query change which is - // not acceptable latency. So for this error code alone, we hard-code a 1 second retry interval. + // FIXME With compensating writes the server sends this error after + // completing a bootstrap. Doing the normal backoff behavior would result + // in waiting up to 5 minutes in between each query change which is not + // acceptable latency. So for this error code alone, we hard-code a 1 + // second retry interval. try_again_interval = std::chrono::milliseconds{1000}; } logger.debug("Will attempt to resume session after %1 milliseconds", try_again_interval.count()); @@ -2635,39 +2680,47 @@ Status ClientImpl::Session::check_received_sync_progress(const SyncProgress& pro const SyncProgress& b = progress; std::string message; if (b.latest_server_version.version < a.latest_server_version.version) { - message = util::format("Latest server version in download messages must be weakly increasing throughout a " + message = util::format("Latest server version in download messages must " + "be weakly increasing throughout a " "session (current: %1, received: %2)", a.latest_server_version.version, b.latest_server_version.version); } if (b.upload.client_version < a.upload.client_version) { - message = util::format("Last integrated client version in download messages must be weakly increasing " + message = util::format("Last integrated client version in download " + "messages must be weakly increasing " "throughout a session (current: %1, received: %2)", a.upload.client_version, b.upload.client_version); } if (b.upload.client_version > m_last_version_available) { - message = util::format("Last integrated client version on server cannot be greater than the latest client " + message = util::format("Last integrated client version on server cannot " + "be greater than the latest client " "version in existence (current: %1, received: %2)", m_last_version_available, b.upload.client_version); } if (b.download.server_version < a.download.server_version) { - message = - util::format("Download cursor must be weakly increasing throughout a session (current: %1, received: %2)", - a.download.server_version, b.download.server_version); + message = util::format("Download cursor must be weakly increasing throughout a " + "session (current: %1, received: %2)", + a.download.server_version, b.download.server_version); } if (b.download.server_version > b.latest_server_version.version) { - message = util::format( - "Download cursor cannot be greater than the latest server version in existence (cursor: %1, latest: %2)", - b.download.server_version, b.latest_server_version.version); + message = util::format("Download cursor cannot be greater than the latest " + "server version in existence " + "(cursor: %1, latest: %2)", + b.download.server_version, b.latest_server_version.version); } if (b.download.last_integrated_client_version < a.download.last_integrated_client_version) { - message = util::format( - "Last integrated client version on the server at the position in the server's history of the download " - "cursor must be weakly increasing throughout a session (current: %1, received: %2)", - a.download.last_integrated_client_version, b.download.last_integrated_client_version); + message = util::format("Last integrated client version on the server at " + "the position in the server's history " + "of the download " + "cursor must be weakly increasing throughout a " + "session (current: %1, received: %2)", + a.download.last_integrated_client_version, b.download.last_integrated_client_version); } if (b.download.last_integrated_client_version > b.upload.client_version) { - message = util::format("Last integrated client version on the server in the position at the server's history " - "of the download cursor cannot be greater than the latest client version integrated " + message = util::format("Last integrated client version on the server in " + "the position at the server's history " + "of the download cursor cannot be greater than the " + "latest client version integrated " "on the server (download: %1, upload: %2)", b.download.last_integrated_client_version, b.upload.client_version); } diff --git a/src/realm/sync/noinst/client_impl_base.hpp b/src/realm/sync/noinst/client_impl_base.hpp index bb5e4031685..ad0d945f84c 100644 --- a/src/realm/sync/noinst/client_impl_base.hpp +++ b/src/realm/sync/noinst/client_impl_base.hpp @@ -483,7 +483,7 @@ class ClientImpl::Connection { void websocket_connected_handler(const std::string& protocol); bool websocket_binary_message_received(util::Span data); void websocket_error_handler(); - bool websocket_closed_handler(bool, Status); + bool websocket_closed_handler(bool, websocket::WebSocketError, std::string_view msg); connection_ident_type get_ident() const noexcept; const ServerEndpoint& get_server_endpoint() const noexcept; @@ -512,9 +512,6 @@ class ClientImpl::Connection { }; struct WebSocketObserverShim; - class IsFatalTag {}; - using IsFatal = util::TaggedBool; - using ReceivedChangesets = ClientProtocol::ReceivedChangesets; template diff --git a/src/realm/sync/noinst/protocol_codec.hpp b/src/realm/sync/noinst/protocol_codec.hpp index df4ffa60b1b..edd8e04cac6 100644 --- a/src/realm/sync/noinst/protocol_codec.hpp +++ b/src/realm/sync/noinst/protocol_codec.hpp @@ -262,7 +262,7 @@ class ClientProtocol { auto message = msg.read_sized_data(message_size); - connection.receive_error_message(sync::ProtocolErrorInfo{error_code, message, try_again}, + connection.receive_error_message(sync::ProtocolErrorInfo{error_code, message, !try_again}, session_ident); // Throws } else if (message_type == "json_error") { // introduced in protocol 4 @@ -275,7 +275,7 @@ class ClientProtocol { auto json = nlohmann::json::parse(json_raw); logger.trace("Error message encoded as json: %1", json_raw); info.client_reset_recovery_is_disabled = json["isRecoveryModeDisabled"]; - info.try_again = json["tryAgain"]; + info.is_fatal = !json["tryAgain"]; info.message = json["message"]; info.log_url = std::make_optional(json["logURL"]); info.should_client_reset = std::make_optional(json["shouldClientReset"]); diff --git a/src/realm/sync/protocol.hpp b/src/realm/sync/protocol.hpp index beb4bc398b4..919d714102a 100644 --- a/src/realm/sync/protocol.hpp +++ b/src/realm/sync/protocol.hpp @@ -7,6 +7,7 @@ #include #include #include +#include // NOTE: The protocol specification is in `/doc/protocol.md` @@ -247,6 +248,9 @@ struct ResumptionDelayInfo { int delay_jitter_divisor = 4; }; +class IsFatalTag {}; +using IsFatal = util::TaggedBool; + struct ProtocolErrorInfo { enum class Action { NoAction, @@ -258,14 +262,16 @@ struct ProtocolErrorInfo { ClientReset, ClientResetNoRecovery, MigrateToFLX, - RevertToPBS + RevertToPBS, + RefreshUser, + RefreshLocation, }; ProtocolErrorInfo() = default; - ProtocolErrorInfo(int error_code, const std::string& msg, bool do_try_again) + ProtocolErrorInfo(int error_code, const std::string& msg, IsFatal is_fatal) : raw_error_code(error_code) , message(msg) - , try_again(do_try_again) + , is_fatal(is_fatal) , client_reset_recovery_is_disabled(false) , should_client_reset(util::none) , server_requests_action(Action::NoAction) @@ -273,7 +279,7 @@ struct ProtocolErrorInfo { } int raw_error_code = 0; std::string message; - bool try_again = false; + IsFatal is_fatal = true; bool client_reset_recovery_is_disabled = false; std::optional should_client_reset; std::optional log_url; @@ -283,11 +289,6 @@ struct ProtocolErrorInfo { std::optional resumption_delay_interval; Action server_requests_action; std::optional migration_query_string; - - bool is_fatal() const - { - return !try_again; - } }; @@ -447,6 +448,10 @@ inline std::ostream& operator<<(std::ostream& o, ProtocolErrorInfo::Action actio return o << "MigrateToFLX"; case ProtocolErrorInfo::Action::RevertToPBS: return o << "RevertToPBS"; + case ProtocolErrorInfo::Action::RefreshUser: + return o << "RefreshUser"; + case ProtocolErrorInfo::Action::RefreshLocation: + return o << "RefreshLocation"; } return o << "Invalid error action: " << int64_t(action); } diff --git a/src/realm/sync/socket_provider.hpp b/src/realm/sync/socket_provider.hpp index 265143c0331..f2c6b3be62f 100644 --- a/src/realm/sync/socket_provider.hpp +++ b/src/realm/sync/socket_provider.hpp @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -238,15 +239,16 @@ struct WebSocketObserver { /// /// @param was_clean Was the TCP connection closed after the WebSocket closing /// handshake was completed. - /// @param status A Status object containing the WebSocket status code and the - /// reason string why the connection was closed. + /// @param error_code The error code received or synthesized when the websocket was closed. + /// @param message The message received in the close frame when the websocket was closed. /// /// @return bool designates whether the WebSocket object has been destroyed /// during the execution of this function. The normal return value is /// True to indicate the WebSocket object is no longer valid. If False /// is returned, the WebSocket object will be destroyed at some point /// in the future. - virtual bool websocket_closed_handler(bool was_clean, Status status) = 0; + virtual bool websocket_closed_handler(bool was_clean, websocket::WebSocketError error_code, + std::string_view message) = 0; }; } // namespace realm::sync diff --git a/test/object-store/c_api/c_api.cpp b/test/object-store/c_api/c_api.cpp index aec86a35cc4..abb0e6db9a9 100644 --- a/test/object-store/c_api/c_api.cpp +++ b/test/object-store/c_api/c_api.cpp @@ -715,16 +715,6 @@ TEST_CASE("C API (non-database)", "[c_api]") { CHECK(ec_check.category() == std::system_category()); CHECK(ec_check.value() == int(error_code.value())); - error_code.assign(ErrorCodes::WebSocketResolveFailedError, - realm::sync::websocket::websocket_error_category()); - error = c_api::to_capi(SystemError(error_code, "").to_status(), message); - CHECK(error.category == realm_sync_error_category_e::RLM_SYNC_ERROR_CATEGORY_WEBSOCKET); - CHECK(error.value == realm_errno::RLM_ERR_WEBSOCKET_RESOLVE_FAILED_ERROR); - - c_api::sync_error_to_error_code(error, &ec_check); - CHECK(ec_check.category() == realm::sync::websocket::websocket_error_category()); - CHECK(ec_check.value() == int(error_code.value())); - error_code = make_error_code(util::error::misc_errors::unknown); error = c_api::to_capi(SystemError(error_code, "").to_status(), message); CHECK(error.category == realm_sync_error_category_e::RLM_SYNC_ERROR_CATEGORY_UNKNOWN); @@ -6183,9 +6173,9 @@ TEST_CASE("C API app: websocket provider", "[sync][app][c_api][baas]") { return m_observer->websocket_binary_message_received(data); } - bool websocket_closed_handler(bool was_clean, Status status) override + bool websocket_closed_handler(bool was_clean, WebSocketError error, std::string_view msg) override { - return m_observer->websocket_closed_handler(was_clean, std::move(status)); + return m_observer->websocket_closed_handler(was_clean, error, msg); } private: @@ -6253,7 +6243,7 @@ TEST_CASE("C API app: websocket provider", "[sync][app][c_api][baas]") { auto test_data = static_cast(userdata); REQUIRE(test_data); auto cb = [callback_copy = callback](Status s) { - realm_sync_socket_callback_complete(callback_copy, static_cast(s.code()), + realm_sync_socket_callback_complete(callback_copy, static_cast(s.code()), s.reason().c_str()); }; test_data->socket_provider->post(std::move(cb)); diff --git a/test/object-store/sync/client_reset.cpp b/test/object-store/sync/client_reset.cpp index 4bc9ebbe980..668d79513dd 100644 --- a/test/object-store/sync/client_reset.cpp +++ b/test/object-store/sync/client_reset.cpp @@ -227,10 +227,6 @@ TEST_CASE("sync: pending client resets are cleared when downloads are complete", 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, SyncError err) { - if (err.get_system_error() == sync::websocket::WebSocketError::websocket_read_error) { - return; - } - if (err.server_requests_action == sync::ProtocolErrorInfo::Action::Warning || err.server_requests_action == sync::ProtocolErrorInfo::Action::Transient) { return; diff --git a/test/sync_fixtures.hpp b/test/sync_fixtures.hpp index d683448cb24..e1fb6910ad7 100644 --- a/test/sync_fixtures.hpp +++ b/test/sync_fixtures.hpp @@ -593,7 +593,7 @@ class MultiClientServerFixture { if (state != ConnectionState::disconnected) return; REALM_ASSERT(error_info); - handler(error_info->status, error_info->is_fatal(), error_info->message); + handler(error_info->status, error_info->is_fatal, error_info->message); }; m_connection_state_change_listeners[client_index] = std::move(handler_wrapped); } @@ -713,7 +713,7 @@ class MultiClientServerFixture { REALM_ASSERT(error); unit_test::TestContext& test_context = m_test_context; test_context.logger->error("Client disconnect: %1: %2 (is_fatal=%3)", - error->status.get_std_error_code(), error->message, error->is_fatal()); + error->status.get_std_error_code(), error->message, error->is_fatal); bool client_error_occurred = true; CHECK_NOT(client_error_occurred); stop(); @@ -1078,7 +1078,7 @@ inline void RealmFixture::setup_error_handler(util::UniqueFunction if (state != ConnectionState::disconnected) return; REALM_ASSERT(error_info); - handler(error_info->status, error_info->is_fatal(), error_info->message); + handler(error_info->status, error_info->is_fatal, error_info->message); }; m_session.set_connection_state_change_listener(std::move(listener)); } diff --git a/test/test_sync.cpp b/test/test_sync.cpp index 5e877f2e5a7..bea1c94a0c1 100644 --- a/test/test_sync.cpp +++ b/test/test_sync.cpp @@ -147,9 +147,8 @@ TEST(Sync_BadVirtualPath) return; REALM_ASSERT(error_info); std::error_code ec = error_info->status.get_std_error_code(); - bool is_fatal = error_info->is_fatal(); CHECK_EQUAL(sync::ProtocolError::illegal_realm_path, ec); - CHECK(is_fatal); + CHECK(error_info->is_fatal); ++nerrors; if (nerrors == 3) fixture.stop(); @@ -786,7 +785,7 @@ struct ExpectChangesetError { return; REALM_ASSERT(error_info); CHECK_EQUAL(error_info->status, ErrorCodes::BadChangeset); - CHECK(!error_info->is_fatal()); + CHECK(!error_info->is_fatal); CHECK_EQUAL(error_info->message, "Failed to transform received changeset: Schema mismatch: " + expected_error); fixture.stop(); @@ -4574,9 +4573,7 @@ TEST(Sync_ServerDiscardDeadConnections) BowlOfStonesSemaphore bowl; auto error_handler = [&](Status status, bool, const std::string&) { - auto ec = status.get_std_error_code(); - bool valid_error = (ec == sync::websocket::WebSocketError::websocket_read_error); - CHECK(valid_error); + CHECK_EQUAL(status, ErrorCodes::ConnectionClosed); bowl.add_stone(); }; fixture.set_client_side_error_handler(std::move(error_handler)); @@ -5172,7 +5169,7 @@ TEST_IF(Sync_SSL_Certificates, false) CHECK(error_info); client_logger->debug( "State change: disconnected, error_code = %1, is_fatal = %2, detailed_message = %3", - error_info->status.get_std_error_code(), error_info->is_fatal(), error_info->message); + error_info->status.get_std_error_code(), error_info->is_fatal, error_info->message); // We expect to get through the SSL handshake but will hit an error due to the wrong token. CHECK_NOT_EQUAL(error_info->status, ErrorCodes::TlsHandshakeFailed); client.shutdown(); @@ -5249,7 +5246,7 @@ TEST(Sync_BadChangeset) return; REALM_ASSERT(error_info); std::error_code ec = error_info->status.get_std_error_code(); - bool is_fatal = error_info->is_fatal(); + bool is_fatal = error_info->is_fatal; CHECK_EQUAL(sync::ProtocolError::bad_changeset, ec); CHECK(is_fatal); did_fail = true; diff --git a/test/test_util_websocket.cpp b/test/test_util_websocket.cpp index 7f2a5ad4e9d..752340c9d5c 100644 --- a/test/test_util_websocket.cpp +++ b/test/test_util_websocket.cpp @@ -171,7 +171,7 @@ class WSConfig : public websocket::Config { std::vector text_messages; std::vector binary_messages; - std::vector> close_messages; + std::vector> close_messages; std::vector ping_messages; std::vector pong_messages; @@ -244,7 +244,8 @@ class WSConfig : public websocket::Config { return true; } - bool websocket_close_message_received(std::error_code error_code, StringData error_message) override + bool websocket_close_message_received(websocket::WebSocketError error_code, + std::string_view error_message) override { close_messages.push_back(std::make_pair(error_code, std::string{error_message})); return true; @@ -389,7 +390,7 @@ TEST(WebSocket_Messages) "close message", 15, handler_no_op); CHECK_EQUAL(config_1.close_messages.size(), 1); - CHECK_EQUAL(config_1.close_messages[0].first.value(), 1000); + CHECK_EQUAL(static_cast(config_1.close_messages[0].first), 1000); CHECK_EQUAL(config_1.close_messages[0].second, "close message"); std::vector message_sizes{1, 2, 100, 125, 126, 127, 128, 200, 1000, 65000, 65535, 65536, 100000, 1000000}; From 2367f93e2a8c038eccebee37e20797f2c82ae720 Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Thu, 3 Aug 2023 18:18:47 -0400 Subject: [PATCH 14/30] add missing header --- src/realm/sync/network/websocket_error.hpp | 58 ++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/realm/sync/network/websocket_error.hpp diff --git a/src/realm/sync/network/websocket_error.hpp b/src/realm/sync/network/websocket_error.hpp new file mode 100644 index 00000000000..5d84385c775 --- /dev/null +++ b/src/realm/sync/network/websocket_error.hpp @@ -0,0 +1,58 @@ +/************************************************************************* + * + * Copyright 2023 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + + + +#pragma once + +#include "realm/error_codes.h" + +namespace realm::sync::websocket { + +enum class WebSocketError { + websocket_ok = RLM_ERR_WEBSOCKET_OK, + websocket_going_away = RLM_ERR_WEBSOCKET_GOINGAWAY, + websocket_protocol_error = RLM_ERR_WEBSOCKET_PROTOCOLERROR, + websocket_unsupported_data = RLM_ERR_WEBSOCKET_UNSUPPORTEDDATA, + websocket_reserved = RLM_ERR_WEBSOCKET_RESERVED, + websocket_no_status_received = RLM_ERR_WEBSOCKET_NOSTATUSRECEIVED, + websocket_abnormal_closure = RLM_ERR_WEBSOCKET_ABNORMALCLOSURE, + websocket_invalid_payload_data = RLM_ERR_WEBSOCKET_INVALIDPAYLOADDATA, + websocket_policy_violation = RLM_ERR_WEBSOCKET_POLICYVIOLATION, + websocket_message_too_big = RLM_ERR_WEBSOCKET_MESSAGETOOBIG, + websocket_invalid_extension = RLM_ERR_WEBSOCKET_INAVALIDEXTENSION, + websocket_internal_server_error = RLM_ERR_WEBSOCKET_INTERNALSERVERERROR, + websocket_tls_handshake_failed = RLM_ERR_WEBSOCKET_TLSHANDSHAKEFAILED, // Used by default WebSocket + + // WebSocket Errors - reported by server + websocket_unauthorized = RLM_ERR_WEBSOCKET_UNAUTHORIZED, + websocket_forbidden = RLM_ERR_WEBSOCKET_FORBIDDEN, + websocket_moved_permanently = RLM_ERR_WEBSOCKET_MOVEDPERMANENTLY, + websocket_client_too_old = RLM_ERR_WEBSOCKET_CLIENT_TOO_OLD, + websocket_client_too_new = RLM_ERR_WEBSOCKET_CLIENT_TOO_NEW, + websocket_protocol_mismatch = RLM_ERR_WEBSOCKET_PROTOCOL_MISMATCH, + + websocket_resolve_failed = RLM_ERR_WEBSOCKET_RESOLVE_FAILED, + websocket_connection_failed = RLM_ERR_WEBSOCKET_CONNECTION_FAILED, + websocket_read_error = RLM_ERR_WEBSOCKET_READ_ERROR, + websocket_write_error = RLM_ERR_WEBSOCKET_WRITE_ERROR, + websocket_retry_error = RLM_ERR_WEBSOCKET_RETRY_ERROR, + websocket_fatal_error = RLM_ERR_WEBSOCKET_FATAL_ERROR, +}; + +} // namespace realm::sync::websocket From 8cb0c68b1f4c0b30fb9796bb2d324a76f2164ea5 Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Thu, 3 Aug 2023 19:20:40 -0400 Subject: [PATCH 15/30] undo weird formatting changes --- src/realm/sync/noinst/client_impl_base.cpp | 176 +++++++++------------ 1 file changed, 71 insertions(+), 105 deletions(-) diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp index 49843f1e93c..d9574a7acc0 100644 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ b/src/realm/sync/noinst/client_impl_base.cpp @@ -1523,8 +1523,7 @@ void Session::integrate_changesets(ClientReplication& repl, const SyncProgress& "received empty download message that was not the last in batch", ProtocolError::bad_progress); } - history.set_sync_progress(progress, &downloadable_bytes, - version_info); // Throws + history.set_sync_progress(progress, &downloadable_bytes, version_info); // Throws return; } @@ -1542,13 +1541,11 @@ void Session::integrate_changesets(ClientReplication& repl, const SyncProgress& } else { logger.debug("%2 remote changesets integrated, producing client version %1", - version_info.sync_version.version, - received_changesets.size()); // Throws + version_info.sync_version.version, received_changesets.size()); // Throws } for (const auto& pending_error : pending_compensating_write_errors) { - logger.info("Reporting compensating write for client version %1 in server " - "version %2: %3", + logger.info("Reporting compensating write for client version %1 in server version %2: %3", pending_error.compensating_write_rejected_client_version, pending_error.compensating_write_server_version, pending_error.message); try { @@ -1605,8 +1602,7 @@ void Session::on_changesets_integrated(version_type client_version, const SyncPr do_recognize_sync_version(client_version); // Allows upload process to resume check_for_download_completion(); // Throws - // If the client migrated from PBS to FLX, create subscriptions when new tables - // are received from server. + // If the client migrated from PBS to FLX, create subscriptions when new tables are received from server. if (auto migration_store = get_migration_store(); migration_store && m_is_flx_sync_session) { auto& flx_subscription_store = *get_flx_subscription_store(); get_migration_store()->create_subscriptions(flx_subscription_store); @@ -1623,8 +1619,7 @@ void Session::on_changesets_integrated(version_type client_version, const SyncPr Session::~Session() { - // REALM_ASSERT_EX(m_state == Unactivated || m_state == Deactivated, - // m_state); + // REALM_ASSERT_EX(m_state == Unactivated || m_state == Deactivated, m_state); } @@ -1645,15 +1640,13 @@ void Session::activate() bool has_pending_client_reset = false; if (REALM_LIKELY(!get_client().is_dry_run())) { - // The reason we need a mutable reference from get_client_reset_config() is - // because we don't want the session to keep a strong reference to the - // client_reset_config->fresh_copy DB. If it did, then the fresh DB would - // stay alive for the duration of this sync session and we want to clean it - // up once the reset is finished. Additionally, the fresh copy will be set - // to a new copy on every reset so there is no reason to keep a reference - // to it. The modification to the client reset config happens via - // std::move(client_reset_config->fresh_copy). If the client reset config - // were a `const &` then this std::move would create another strong + // The reason we need a mutable reference from get_client_reset_config() is because we + // don't want the session to keep a strong reference to the client_reset_config->fresh_copy + // DB. If it did, then the fresh DB would stay alive for the duration of this sync session + // and we want to clean it up once the reset is finished. Additionally, the fresh copy will + // be set to a new copy on every reset so there is no reason to keep a reference to it. + // The modification to the client reset config happens via std::move(client_reset_config->fresh_copy). + // If the client reset config were a `const &` then this std::move would create another strong // reference which we don't want to happen. util::Optional& client_reset_config = get_client_reset_config(); @@ -1685,12 +1678,9 @@ void Session::activate() m_download_progress = m_progress.download; REALM_ASSERT_3(m_last_version_available, >=, m_progress.upload.client_version); - logger.debug("last_version_available = %1", - m_last_version_available); // Throws - logger.debug("progress_server_version = %1", - m_progress.download.server_version); // Throws - logger.debug("progress_client_version = %1", - m_progress.download.last_integrated_client_version); // Throws + logger.debug("last_version_available = %1", m_last_version_available); // Throws + logger.debug("progress_server_version = %1", m_progress.download.server_version); // Throws + logger.debug("progress_client_version = %1", m_progress.download.last_integrated_client_version); // Throws reset_protocol_state(); m_state = Active; @@ -1808,8 +1798,7 @@ void Session::send_message() if (m_error_to_send) return send_json_error_message(); // Throws - // Stop sending upload, mark and query messages when the client detects an - // error. + // Stop sending upload, mark and query messages when the client detects an error. if (m_client_error) { return; } @@ -1832,8 +1821,7 @@ void Session::send_message() return true; } - // Do not allow upload if the last query sent is the sentinel one used by - // the migration store. + // Do not allow upload if the last query sent is the sentinel one used by the migration store. return m_last_sent_flx_query_version != *sentinel_query_version; }; @@ -1896,23 +1884,19 @@ void Session::send_bind_message() if (!bind_json_data.empty()) { json_data_dump = bind_json_data.dump(); } - logger.debug("Sending: BIND(session_ident=%1, " - "need_client_file_ident=%2, is_subserver=%3, " - "json_data=\"%4\")", - session_ident, need_client_file_ident, is_subserver, json_data_dump); + logger.debug( + "Sending: BIND(session_ident=%1, need_client_file_ident=%2, is_subserver=%3, json_data=\"%4\")", + session_ident, need_client_file_ident, is_subserver, json_data_dump); } protocol.make_flx_bind_message(protocol_version, out, session_ident, bind_json_data, empty_access_token, - need_client_file_ident, - is_subserver); // Throws + need_client_file_ident, is_subserver); // Throws } else { std::string server_path = get_virt_path(); - logger.debug("Sending: BIND(session_ident=%1, need_client_file_ident=%2, " - "is_subserver=%3, server_path=%4)", + logger.debug("Sending: BIND(session_ident=%1, need_client_file_ident=%2, is_subserver=%3, server_path=%4)", session_ident, need_client_file_ident, is_subserver, server_path); protocol.make_pbs_bind_message(protocol_version, out, session_ident, server_path, empty_access_token, - need_client_file_ident, - is_subserver); // Throws + need_client_file_ident, is_subserver); // Throws } m_conn.initiate_write_message(out, this); // Throws @@ -1940,30 +1924,26 @@ void Session::send_ident_message() if (m_is_flx_sync_session) { const auto active_query_set = get_flx_subscription_store()->get_active(); const auto active_query_body = active_query_set.to_ext_json(); - logger.debug("Sending: IDENT(client_file_ident=%1, client_file_ident_salt=%2, " - "scan_server_version=%3, scan_client_version=%4, " - "latest_server_version=%5, " - "latest_server_version_salt=%6, query_version=%7, query_size=%8, " - "query=\"%9\")", - m_client_file_ident.ident, m_client_file_ident.salt, m_progress.download.server_version, - m_progress.download.last_integrated_client_version, m_progress.latest_server_version.version, - m_progress.latest_server_version.salt, active_query_set.version(), active_query_body.size(), - active_query_body); // Throws + logger.debug( + "Sending: IDENT(client_file_ident=%1, client_file_ident_salt=%2, scan_server_version=%3, " + "scan_client_version=%4, " + "latest_server_version=%5, latest_server_version_salt=%6, query_version=%7, query_size=%8, query=\"%9\")", + m_client_file_ident.ident, m_client_file_ident.salt, m_progress.download.server_version, + m_progress.download.last_integrated_client_version, m_progress.latest_server_version.version, + m_progress.latest_server_version.salt, active_query_set.version(), active_query_body.size(), + active_query_body); // Throws protocol.make_flx_ident_message(out, session_ident, m_client_file_ident, m_progress, - active_query_set.version(), - active_query_body); // Throws + active_query_set.version(), active_query_body); // Throws m_last_sent_flx_query_version = active_query_set.version(); } else { - logger.debug("Sending: IDENT(client_file_ident=%1, client_file_ident_salt=%2, " - "scan_server_version=%3, scan_client_version=%4, " - "latest_server_version=%5, " - "latest_server_version_salt=%6)", + logger.debug("Sending: IDENT(client_file_ident=%1, client_file_ident_salt=%2, scan_server_version=%3, " + "scan_client_version=%4, " + "latest_server_version=%5, latest_server_version_salt=%6)", m_client_file_ident.ident, m_client_file_ident.salt, m_progress.download.server_version, m_progress.download.last_integrated_client_version, m_progress.latest_server_version.version, - m_progress.latest_server_version.salt); // Throws - protocol.make_pbs_ident_message(out, session_ident, m_client_file_ident, - m_progress); // Throws + m_progress.latest_server_version.salt); // Throws + protocol.make_pbs_ident_message(out, session_ident, m_client_file_ident, m_progress); // Throws } m_conn.initiate_write_message(out, this); // Throws @@ -1988,8 +1968,7 @@ void Session::send_query_change_message() auto sub_store = get_flx_subscription_store(); auto latest_sub_set = sub_store->get_by_version(m_pending_flx_sub_set->query_version); auto latest_queries = latest_sub_set.to_ext_json(); - logger.debug("Sending: QUERY(query_version=%1, query_size=%2, query=\"%3\", " - "snapshot_version=%4)", + logger.debug("Sending: QUERY(query_version=%1, query_size=%2, query=\"%3\", snapshot_version=%4)", latest_sub_set.version(), latest_queries.size(), latest_queries, latest_sub_set.snapshot_version()); OutputBuffer& out = m_conn.get_output_buffer(); @@ -2020,8 +1999,7 @@ void Session::send_upload_message() m_last_sent_flx_query_version, m_upload_progress.client_version); } if (m_pending_flx_sub_set && m_pending_flx_sub_set->snapshot_version < m_upload_target_version) { - logger.trace("Limiting UPLOAD message up to version %1 to send QUERY " - "version %2", + logger.trace("Limiting UPLOAD message up to version %1 to send QUERY version %2", m_pending_flx_sub_set->snapshot_version, m_pending_flx_sub_set->query_version); target_upload_version = m_pending_flx_sub_set->snapshot_version; } @@ -2057,8 +2035,7 @@ void Session::send_upload_message() logger.debug("Fetching changeset for upload (client_version=%1, server_version=%2, " "changeset_size=%3, origin_timestamp=%4, origin_file_ident=%5)", uc.progress.client_version, uc.progress.last_integrated_server_version, uc.changeset.size(), - uc.origin_timestamp, - uc.origin_file_ident); // Throws + uc.origin_timestamp, uc.origin_file_ident); // Throws if (logger.would_log(util::Logger::Level::trace)) { BinaryData changeset_data = uc.changeset.get_first_chunk(); if (changeset_data.size() < 1024) { @@ -2264,11 +2241,11 @@ Status Session::receive_ident_message(SaltedFileIdent client_file_ident) return false; } - // ClientResetOperation::finalize() will return true only if the operation - // actually did a client reset. It may choose not to do a reset if the - // local Realm does not exist at this point (in that case there is nothing - // to reset). But in any case, we must clean up m_client_reset_operation at - // this point as sync should be able to continue from this point forward. + // ClientResetOperation::finalize() will return true only if the operation actually did + // a client reset. It may choose not to do a reset if the local Realm does not exist + // at this point (in that case there is nothing to reset). But in any case, we must + // clean up m_client_reset_operation at this point as sync should be able to continue from + // this point forward. auto client_reset_operation = std::move(m_client_reset_operation); util::UniqueFunction on_flx_subscription_complete = [this](int64_t version) { this->on_flx_sync_version_complete(version); @@ -2281,8 +2258,7 @@ Status Session::receive_ident_message(SaltedFileIdent client_file_ident) realm::VersionID client_reset_new_version = client_reset_operation->get_client_reset_new_version(); // 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 SaltedFileIdent client_file_ident; bool has_pending_client_reset = false; @@ -2295,16 +2271,14 @@ Status Session::receive_ident_message(SaltedFileIdent client_file_ident) REALM_ASSERT_EX(m_progress.upload.client_version == 0, m_progress.upload.client_version); REALM_ASSERT_EX(m_progress.upload.last_integrated_server_version == 0, m_progress.upload.last_integrated_server_version); - logger.trace("last_version_available = %1", - m_last_version_available); // Throws + logger.trace("last_version_available = %1", m_last_version_available); // Throws m_upload_target_version = m_last_version_available; m_upload_progress = m_progress.upload; m_download_progress = m_progress.download; - // In recovery mode, there may be new changesets to upload and nothing left - // to download. In FLX DiscardLocal mode, there may be new commits due to - // subscription handling. For both, we want to allow uploads again without - // needing external changes to download first. + // In recovery mode, there may be new changesets to upload and nothing left to download. + // In FLX DiscardLocal mode, there may be new commits due to subscription handling. + // For both, we want to allow uploads again without needing external changes to download first. m_allow_upload = true; REALM_ASSERT_EX(m_last_version_selected_for_upload == 0, m_last_version_selected_for_upload); @@ -2314,8 +2288,7 @@ Status Session::receive_ident_message(SaltedFileIdent client_file_ident) handle_pending_client_reset_acknowledgement(); } - // If a migration or rollback is in progress, mark it complete when client - // reset is completed. + // If a migration or rollback is in progress, mark it complete when client reset is completed. if (auto migration_store = get_migration_store()) { migration_store->complete_migration_or_rollback(); } @@ -2362,20 +2335,17 @@ Status Session::receive_download_message(const SyncProgress& progress, std::uint batch_state = DownloadBatchState::SteadyState; } - logger.debug("Received: DOWNLOAD(download_server_version=%1, " - "download_client_version=%2, " + logger.debug("Received: DOWNLOAD(download_server_version=%1, download_client_version=%2, " "latest_server_version=%3, latest_server_version_salt=%4, " - "upload_client_version=%5, upload_server_version=%6, " - "downloadable_bytes=%7, " + "upload_client_version=%5, upload_server_version=%6, downloadable_bytes=%7, " "last_in_batch=%8, query_version=%9, num_changesets=%10, ...)", progress.download.server_version, progress.download.last_integrated_client_version, progress.latest_server_version.version, progress.latest_server_version.salt, progress.upload.client_version, progress.upload.last_integrated_server_version, downloadable_bytes, - batch_state != DownloadBatchState::MoreToCome, query_version, - received_changesets.size()); // Throws + batch_state != DownloadBatchState::MoreToCome, query_version, received_changesets.size()); // Throws - // Ignore download messages when the client detects an error. This is to - // prevent transforming the same bad changeset over and over again. + // Ignore download messages when the client detects an error. This is to prevent transforming the same bad_syntax + // changeset over and over again. if (m_client_error) { logger.debug("Ignoring download message because the client detected an integration error"); return Status::OK(); @@ -2441,8 +2411,7 @@ Status Session::receive_download_message(const SyncProgress& progress, std::uint return Status::OK(); } - initiate_integrate_changesets(downloadable_bytes, batch_state, progress, - received_changesets); // Throws + initiate_integrate_changesets(downloadable_bytes, batch_state, progress, received_changesets); // Throws hook_action = call_debug_hook(SyncClientHookEvent::DownloadMessageIntegrated, progress, query_version, batch_state, received_changesets.size()); @@ -2451,8 +2420,8 @@ Status Session::receive_download_message(const SyncProgress& progress, std::uint } REALM_ASSERT_EX(hook_action == SyncClientHookAction::NoAction, hook_action); - // When we receive a DOWNLOAD message successfully, we can clear the backoff - // timer value used to reconnect after a retryable session error. + // When we receive a DOWNLOAD message successfully, we can clear the backoff timer value used to reconnect + // after a retryable session error. clear_resumption_delay_state(); return Status::OK(); } @@ -2513,7 +2482,7 @@ Status Session::receive_unbound_message() // The deactivation process completes when the unbinding process // completes. complete_deactivation(); // Throws - // Life cycle state is now Deactivated + // Life cycle state is now Deactivated } return Status::OK(); // Success @@ -2537,8 +2506,7 @@ Status Session::receive_query_error_message(int error_code, std::string_view mes Status Session::receive_error_message(const ProtocolErrorInfo& info) { logger.info("Received: ERROR \"%1\" (error_code=%2, is_fatal=%3, error_action=%4)", info.message, - info.raw_error_code, info.is_fatal, - info.server_requests_action); // Throws + info.raw_error_code, info.is_fatal, info.server_requests_action); // Throws bool legal_at_this_time = (m_bind_message_sent && !m_error_message_received && !m_unbound_message_received); if (REALM_UNLIKELY(!legal_at_this_time)) { @@ -2557,8 +2525,8 @@ Status Session::receive_error_message(const ProtocolErrorInfo& info) info.raw_error_code)}; } - // Can't process debug hook actions once the Session is undergoing - // deactivation, since the SessionWrapper may not be available + // Can't process debug hook actions once the Session is undergoing deactivation, since + // the SessionWrapper may not be available if (m_state == Active) { auto debug_action = call_debug_hook(SyncClientHookEvent::ErrorMessageReceived, info); if (debug_action == SyncClientHookAction::EarlyReturn) { @@ -2566,12 +2534,11 @@ Status Session::receive_error_message(const ProtocolErrorInfo& info) } } - // For compensating write errors, we need to defer raising them to the SDK - // until after the server version containing the compensating write has - // appeared in a download message. + // For compensating write errors, we need to defer raising them to the SDK until after the server version + // containing the compensating write has appeared in a download message. if (error_code == ProtocolError::compensating_write) { - // If the client is not active, the compensating writes will not be - // processed now, but will be sent again the next time the client connects + // If the client is not active, the compensating writes will not be processed now, but will be + // sent again the next time the client connects if (m_state == Active) { m_pending_compensating_write_errors.push_back(info); } @@ -2593,16 +2560,15 @@ void Session::suspend(const SessionErrorInfo& info) // Detect completion of the unbinding process if (m_unbind_message_send_complete && m_error_message_received) { - // The fact that the UNBIND message has been sent, but we are not being - // suspended because we received an ERROR message implies that the - // deactivation process must have been initiated, so this session must be - // in the Deactivating state. + // The fact that the UNBIND message has been sent, but we are not being suspended because + // we received an ERROR message implies that the deactivation process must + // have been initiated, so this session must be in the Deactivating state. REALM_ASSERT_EX(m_state == Deactivating, m_state); // The deactivation process completes when the unbinding process // completes. complete_deactivation(); // Throws - // Life cycle state is now Deactivated + // Life cycle state is now Deactivated } // Notify the application of the suspension of the session if the session is From f1e5fce324625863b2d9963a0897072d833a9a63 Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Thu, 3 Aug 2023 19:32:02 -0400 Subject: [PATCH 16/30] clang format again --- src/realm/sync/network/websocket_error.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/realm/sync/network/websocket_error.hpp b/src/realm/sync/network/websocket_error.hpp index 5d84385c775..aba801adcba 100644 --- a/src/realm/sync/network/websocket_error.hpp +++ b/src/realm/sync/network/websocket_error.hpp @@ -17,7 +17,6 @@ **************************************************************************/ - #pragma once #include "realm/error_codes.h" From e6e8fa9f2d7816913c7464ce9242552a002029a3 Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Fri, 4 Aug 2023 10:28:52 -0400 Subject: [PATCH 17/30] whoops --- src/realm/sync/client_base.hpp | 4 ++-- src/realm/sync/noinst/client_impl_base.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/realm/sync/client_base.hpp b/src/realm/sync/client_base.hpp index e3cb0f59096..e72d1d36a8c 100644 --- a/src/realm/sync/client_base.hpp +++ b/src/realm/sync/client_base.hpp @@ -251,8 +251,8 @@ struct SessionErrorInfo : public ProtocolErrorInfo { { } - SessionErrorInfo(Status status, bool try_again) - : ProtocolErrorInfo(status.get_std_error_code().value(), status.reason(), try_again) + SessionErrorInfo(Status status, IsFatal is_fatal) + : ProtocolErrorInfo(status.get_std_error_code().value(), status.reason(), is_fatal) , status(std::move(status)) { } diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp index d9574a7acc0..5f4b967aa9c 100644 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ b/src/realm/sync/noinst/client_impl_base.cpp @@ -1131,7 +1131,7 @@ void Connection::close_due_to_client_side_error(Status status, IsFatal is_fatal, void Connection::close_due_to_transient_error(Status status, ConnectionTerminationReason reason) { logger.info("Connection closed due to transient error: %1", status); // Throws - SessionErrorInfo error_info{std::move(status), true}; + SessionErrorInfo error_info{std::move(status), IsFatal{false}}; error_info.server_requests_action = ProtocolErrorInfo::Action::Transient; involuntary_disconnect(std::move(error_info), reason); // Throw From 7d4ead01987f9116cbc9a8e8a5d49df4d5e4309b Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Fri, 4 Aug 2023 12:55:34 -0400 Subject: [PATCH 18/30] fix a bunch of IsFatal mixups --- src/realm/object-store/c_api/sync.cpp | 2 +- src/realm/object-store/sync/sync_session.cpp | 3 +-- src/realm/sync/client.cpp | 2 +- src/realm/sync/network/websocket_error.hpp | 4 ++++ src/realm/sync/noinst/client_impl_base.cpp | 7 +++---- src/realm/sync/noinst/client_impl_base.hpp | 3 +-- src/realm/sync/noinst/protocol_codec.hpp | 6 +++--- src/realm/sync/protocol.hpp | 2 +- test/object-store/sync/client_reset.cpp | 2 +- test/object-store/sync/session/session.cpp | 16 +++++++--------- .../sync/session/wait_for_completion.cpp | 8 +++----- 11 files changed, 26 insertions(+), 29 deletions(-) diff --git a/src/realm/object-store/c_api/sync.cpp b/src/realm/object-store/c_api/sync.cpp index 39f39281019..0ab903dd1c3 100644 --- a/src/realm/object-store/c_api/sync.cpp +++ b/src/realm/object-store/c_api/sync.cpp @@ -896,7 +896,7 @@ RLM_API void realm_sync_session_handle_error_for_testing(const realm_sync_sessio std::error_code err; sync_error_to_error_code(sync_error, &err); SyncSession::OnlyForTesting::handle_error(*session->get(), - sync::SessionErrorInfo{Status{err, error_message}, !is_fatal}); + sync::SessionErrorInfo{Status{err, error_message}, IsFatal{is_fatal}}); } } // namespace realm::c_api diff --git a/src/realm/object-store/sync/sync_session.cpp b/src/realm/object-store/sync/sync_session.cpp index 26366e2473d..2b79f34501a 100644 --- a/src/realm/object-store/sync/sync_session.cpp +++ b/src/realm/object-store/sync/sync_session.cpp @@ -579,11 +579,10 @@ void SyncSession::handle_fresh_realm_downloaded(DBRef db, Status status, } lock.unlock(); - const bool try_again = false; sync::SessionErrorInfo synthetic( Status{ErrorCodes::AutoClientResetFailed, util::format("A fatal error occurred during client reset: '%1'", status.reason())}, - try_again); + sync::IsFatal{true}); handle_error(synthetic); return; } diff --git a/src/realm/sync/client.cpp b/src/realm/sync/client.cpp index 8a4287dcd74..cd3f23884f7 100644 --- a/src/realm/sync/client.cpp +++ b/src/realm/sync/client.cpp @@ -1000,7 +1000,7 @@ SyncClientHookAction SessionImpl::call_debug_hook(const SyncClientHookData& data auto action = m_wrapper.m_debug_hook(data); switch (action) { case realm::SyncClientHookAction::SuspendWithRetryableError: { - SessionErrorInfo err_info(Status{ErrorCodes::RuntimeError, "hook requested error"}, true); + SessionErrorInfo err_info(Status{ErrorCodes::RuntimeError, "hook requested error"}, IsFatal{false}); err_info.server_requests_action = ProtocolErrorInfo::Action::Transient; auto err_processing_err = receive_error_message(err_info); diff --git a/src/realm/sync/network/websocket_error.hpp b/src/realm/sync/network/websocket_error.hpp index aba801adcba..2c0dbbd0bc7 100644 --- a/src/realm/sync/network/websocket_error.hpp +++ b/src/realm/sync/network/websocket_error.hpp @@ -21,6 +21,8 @@ #include "realm/error_codes.h" +#include + namespace realm::sync::websocket { enum class WebSocketError { @@ -54,4 +56,6 @@ enum class WebSocketError { websocket_fatal_error = RLM_ERR_WEBSOCKET_FATAL_ERROR, }; +std::ostream& operator<<(std::ostream& os, WebSocketError code); + } // namespace realm::sync::websocket diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp index 5f4b967aa9c..7484ac27d03 100644 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ b/src/realm/sync/noinst/client_impl_base.cpp @@ -505,8 +505,7 @@ bool Connection::websocket_closed_handler(bool was_clean, WebSocketError error_c logger.debug("Received websocket close message after connection was force closed"); return false; } - logger.info("Closing the websocket with error code=%1, message=%2, was_clean='%2'", static_cast(error_code), - msg, was_clean); + logger.info("Closing the websocket with error code=%1, message='%2', was_clean=%3", error_code, msg, was_clean); switch (error_code) { case WebSocketError::websocket_ok: @@ -1695,7 +1694,7 @@ void Session::activate() logger.error("Error integrating bootstrap changesets: %1", error.what()); m_suspended = true; m_conn.one_less_active_unsuspended_session(); // Throws - on_suspended(SessionErrorInfo{Status{error.code(), error.what()}, false}); + on_suspended(SessionErrorInfo{Status{error.code(), error.what()}, IsFatal{true}}); } if (has_pending_client_reset) { @@ -2304,7 +2303,7 @@ Status Session::receive_ident_message(SaltedFileIdent client_file_ident) catch (const std::exception& e) { auto err_msg = util::format("A fatal error occurred during client reset: '%1'", e.what()); logger.error(err_msg.c_str()); - SessionErrorInfo err_info(Status{ErrorCodes::AutoClientResetFailed, err_msg}, false); + SessionErrorInfo err_info(Status{ErrorCodes::AutoClientResetFailed, err_msg}, IsFatal{true}); suspend(err_info); return Status::OK(); } diff --git a/src/realm/sync/noinst/client_impl_base.hpp b/src/realm/sync/noinst/client_impl_base.hpp index 0a61404a772..b92f04932b9 100644 --- a/src/realm/sync/noinst/client_impl_base.hpp +++ b/src/realm/sync/noinst/client_impl_base.hpp @@ -1309,8 +1309,7 @@ void ClientImpl::Connection::for_each_active_session(H handler) inline void ClientImpl::Connection::voluntary_disconnect() { m_reconnect_info.update(ConnectionTerminationReason::closed_voluntarily, std::nullopt); - constexpr bool try_again = true; - SessionErrorInfo error_info{Status{ErrorCodes::ConnectionClosed, "Connection closed"}, try_again}; + SessionErrorInfo error_info{Status{ErrorCodes::ConnectionClosed, "Connection closed"}, IsFatal{false}}; error_info.server_requests_action = ProtocolErrorInfo::Action::Transient; disconnect(std::move(error_info)); // Throws diff --git a/src/realm/sync/noinst/protocol_codec.hpp b/src/realm/sync/noinst/protocol_codec.hpp index edd8e04cac6..b108e767a2c 100644 --- a/src/realm/sync/noinst/protocol_codec.hpp +++ b/src/realm/sync/noinst/protocol_codec.hpp @@ -252,7 +252,7 @@ class ClientProtocol { else if (message_type == "error") { auto error_code = msg.read_next(); auto message_size = msg.read_next(); - auto try_again = msg.read_next(); + auto is_fatal = sync::IsFatal{!msg.read_next()}; auto session_ident = msg.read_next('\n'); bool unknown_error = !sync::get_protocol_error_message(error_code); @@ -262,7 +262,7 @@ class ClientProtocol { auto message = msg.read_sized_data(message_size); - connection.receive_error_message(sync::ProtocolErrorInfo{error_code, message, !try_again}, + connection.receive_error_message(sync::ProtocolErrorInfo{error_code, message, is_fatal}, session_ident); // Throws } else if (message_type == "json_error") { // introduced in protocol 4 @@ -275,7 +275,7 @@ class ClientProtocol { auto json = nlohmann::json::parse(json_raw); logger.trace("Error message encoded as json: %1", json_raw); info.client_reset_recovery_is_disabled = json["isRecoveryModeDisabled"]; - info.is_fatal = !json["tryAgain"]; + info.is_fatal = sync::IsFatal{!json["tryAgain"]}; info.message = json["message"]; info.log_url = std::make_optional(json["logURL"]); info.should_client_reset = std::make_optional(json["shouldClientReset"]); diff --git a/src/realm/sync/protocol.hpp b/src/realm/sync/protocol.hpp index 919d714102a..2252af3a36e 100644 --- a/src/realm/sync/protocol.hpp +++ b/src/realm/sync/protocol.hpp @@ -279,7 +279,7 @@ struct ProtocolErrorInfo { } int raw_error_code = 0; std::string message; - IsFatal is_fatal = true; + IsFatal is_fatal = IsFatal{true}; bool client_reset_recovery_is_disabled = false; std::optional should_client_reset; std::optional log_url; diff --git a/test/object-store/sync/client_reset.cpp b/test/object-store/sync/client_reset.cpp index 6f2e7632904..a972b836196 100644 --- a/test/object-store/sync/client_reset.cpp +++ b/test/object-store/sync/client_reset.cpp @@ -898,7 +898,7 @@ TEST_CASE("sync: client reset", "[sync][pbs][client reset][baas]") { REQUIRE(session); } sync::SessionErrorInfo synthetic(Status{ErrorCodes::SyncClientResetRequired, "A fake client reset error"}, - false); + sync::IsFatal{true}); synthetic.server_requests_action = sync::ProtocolErrorInfo::Action::ClientReset; SyncSession::OnlyForTesting::handle_error(*session, std::move(synthetic)); diff --git a/test/object-store/sync/session/session.cpp b/test/object-store/sync/session/session.cpp index 9c479b06669..d33aa24c8b0 100644 --- a/test/object-store/sync/session/session.cpp +++ b/test/object-store/sync/session/session.cpp @@ -401,7 +401,7 @@ TEST_CASE("sync: error handling", "[sync][session]") { SECTION("Doesn't treat unknown system errors as being fatal") { std::error_code code = std::error_code{EBADF, std::generic_category()}; - sync::SessionErrorInfo err{Status{code, "Not a real error message"}, true}; + sync::SessionErrorInfo err{Status{code, "Not a real error message"}, sync::IsFatal{false}}; err.server_requests_action = ProtocolErrorInfo::Action::Transient; SyncSession::OnlyForTesting::handle_error(*session, std::move(err)); CHECK(!sessions_are_inactive(*session)); @@ -431,7 +431,8 @@ TEST_CASE("sync: error handling", "[sync][session]") { } sync::SessionErrorInfo initial_error{ - Status{std::error_code{code, realm::sync::protocol_error_category()}, "Something bad happened"}, true}; + Status{std::error_code{code, realm::sync::protocol_error_category()}, "Something bad happened"}, + sync::IsFatal{false}}; initial_error.server_requests_action = ProtocolErrorInfo::Action::ClientReset; std::time_t just_before_raw = std::time(nullptr); SyncSession::OnlyForTesting::handle_error(*session, std::move(initial_error)); @@ -476,7 +477,6 @@ struct RegularUser { TEMPLATE_TEST_CASE("sync: stop policy behavior", "[sync][session]", RegularUser) { - using ProtocolError = realm::sync::ProtocolError; const std::string dummy_auth_url = "https://realm.example.org"; if (!EventLoop::has_implementation()) return; @@ -551,9 +551,8 @@ TEMPLATE_TEST_CASE("sync: stop policy behavior", "[sync][session]", RegularUser) } SECTION("transitions to Inactive if a fatal error occurs") { - std::error_code code = - std::error_code{static_cast(ProtocolError::bad_syntax), realm::sync::protocol_error_category()}; - sync::SessionErrorInfo err{Status{code, "Not a real error message"}, false}; + sync::SessionErrorInfo err{Status{ErrorCodes::SyncProtocolInvariantFailed, "Not a real error message"}, + sync::IsFatal{true}}; err.server_requests_action = realm::sync::ProtocolErrorInfo::Action::ProtocolViolation; SyncSession::OnlyForTesting::handle_error(*session, std::move(err)); CHECK(sessions_are_inactive(*session)); @@ -563,9 +562,8 @@ TEMPLATE_TEST_CASE("sync: stop policy behavior", "[sync][session]", RegularUser) SECTION("ignores non-fatal errors and does not transition to Inactive") { // Fire a simulated *non-fatal* error. - std::error_code code = - std::error_code{static_cast(ProtocolError::other_error), realm::sync::protocol_error_category()}; - sync::SessionErrorInfo err{Status{code, "Not a real error message"}, true}; + sync::SessionErrorInfo err{Status{ErrorCodes::ConnectionClosed, "Not a real error message"}, + sync::IsFatal{false}}; err.server_requests_action = realm::sync::ProtocolErrorInfo::Action::Transient; SyncSession::OnlyForTesting::handle_error(*session, std::move(err)); REQUIRE(session->state() == SyncSession::State::Dying); diff --git a/test/object-store/sync/session/wait_for_completion.cpp b/test/object-store/sync/session/wait_for_completion.cpp index 9f4ec49ae81..1ad20b72e0a 100644 --- a/test/object-store/sync/session/wait_for_completion.cpp +++ b/test/object-store/sync/session/wait_for_completion.cpp @@ -93,23 +93,21 @@ TEST_CASE("SyncSession: wait_for_download_completion() API", "[sync][pbs][sessio } SECTION("aborts properly when queued and the session errors out") { - using ProtocolError = realm::sync::ProtocolError; auto user = sync_manager->get_user("user-async-wait-download-4", ENCODE_FAKE_JWT("not_a_real_token"), ENCODE_FAKE_JWT("not_a_real_token"), dummy_auth_url, dummy_device_id); std::atomic error_count(0); std::shared_ptr session = sync_session(user, "/async-wait-download-4", [&](auto, auto) { ++error_count; }); - std::error_code code = - std::error_code{static_cast(ProtocolError::bad_syntax), realm::sync::protocol_error_category()}; // Register the download-completion notification session->wait_for_download_completion([&](Status status) { - REQUIRE(status.get_std_error_code() == code); + REQUIRE(status == ErrorCodes::SyncProtocolInvariantFailed); handler_called = true; }); REQUIRE(handler_called == false); // Now trigger an error - sync::SessionErrorInfo err{Status{code, "Not a real error message"}, false}; + sync::SessionErrorInfo err{Status{ErrorCodes::SyncProtocolInvariantFailed, "Not a real error message"}, + sync::IsFatal{false}}; err.server_requests_action = sync::ProtocolErrorInfo::Action::ProtocolViolation; SyncSession::OnlyForTesting::handle_error(*session, std::move(err)); EventLoop::main().run_until([&] { From 8ecbed3b170ae29f4903db9919966f94e4e90b89 Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Fri, 4 Aug 2023 13:17:38 -0400 Subject: [PATCH 19/30] handle redirects --- src/realm/sync/noinst/client_impl_base.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp index 7484ac27d03..9368fa23c0f 100644 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ b/src/realm/sync/noinst/client_impl_base.cpp @@ -587,8 +587,13 @@ bool Connection::websocket_closed_handler(bool was_clean, WebSocketError error_c ConnectionTerminationReason::http_response_says_nonfatal_error); break; } - case WebSocketError::websocket_moved_permanently: - [[fallthrough]]; + case WebSocketError::websocket_moved_permanently: { + SessionErrorInfo error_info({ErrorCodes::ConnectionClosed, msg}, IsFatal{false}); + error_info.server_requests_action = ProtocolErrorInfo::Action::RefreshLocation; + involuntary_disconnect(std::move(error_info), + ConnectionTerminationReason::http_response_says_nonfatal_error); + break; + } case WebSocketError::websocket_abnormal_closure: { SessionErrorInfo error_info({ErrorCodes::ConnectionClosed, msg}, IsFatal{false}); error_info.server_requests_action = ProtocolErrorInfo::Action::RefreshUser; From 982c9d58472ed4f96611b4350f5d5af76e876518 Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Fri, 4 Aug 2023 14:46:32 -0400 Subject: [PATCH 20/30] formatting fixups --- src/realm/sync/noinst/client_impl_base.cpp | 33 +++++++++++----------- src/realm/sync/noinst/protocol_codec.hpp | 2 ++ 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp index 9368fa23c0f..53aad7be652 100644 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ b/src/realm/sync/noinst/client_impl_base.cpp @@ -1295,8 +1295,7 @@ void Connection::receive_error_message(const ProtocolErrorInfo& info, session_id return; } - logger.info("Received: ERROR \"%1\" (error_code=%2, is_fatal=%3, " - "session_ident=%4, error_action=%5)", + logger.info("Received: ERROR \"%1\" (error_code=%2, is_fatal=%3, session_ident=%4, error_action=%5)", info.message, info.raw_error_code, info.is_fatal, session_ident, info.server_requests_action); // Throws @@ -1682,9 +1681,10 @@ void Session::activate() m_download_progress = m_progress.download; REALM_ASSERT_3(m_last_version_available, >=, m_progress.upload.client_version); - logger.debug("last_version_available = %1", m_last_version_available); // Throws - logger.debug("progress_server_version = %1", m_progress.download.server_version); // Throws - logger.debug("progress_client_version = %1", m_progress.download.last_integrated_client_version); // Throws + logger.debug("last_version_available = %1", m_last_version_available); // Throws + logger.debug("progress_server_version = %1", m_progress.download.server_version); // Throws + logger.debug("progress_client_version = %1", + m_progress.download.last_integrated_client_version); // Throws reset_protocol_state(); m_state = Active; @@ -1928,22 +1928,21 @@ void Session::send_ident_message() if (m_is_flx_sync_session) { const auto active_query_set = get_flx_subscription_store()->get_active(); const auto active_query_body = active_query_set.to_ext_json(); - logger.debug( - "Sending: IDENT(client_file_ident=%1, client_file_ident_salt=%2, scan_server_version=%3, " - "scan_client_version=%4, " - "latest_server_version=%5, latest_server_version_salt=%6, query_version=%7, query_size=%8, query=\"%9\")", - m_client_file_ident.ident, m_client_file_ident.salt, m_progress.download.server_version, - m_progress.download.last_integrated_client_version, m_progress.latest_server_version.version, - m_progress.latest_server_version.salt, active_query_set.version(), active_query_body.size(), - active_query_body); // Throws + logger.debug("Sending: IDENT(client_file_ident=%1, client_file_ident_salt=%2, " + "scan_server_version=%3, scan_client_version=%4, latest_server_version=%5, " + "latest_server_version_salt=%6, query_version=%7, query_size=%8, query=\"%9\")", + m_client_file_ident.ident, m_client_file_ident.salt, m_progress.download.server_version, + m_progress.download.last_integrated_client_version, m_progress.latest_server_version.version, + m_progress.latest_server_version.salt, active_query_set.version(), active_query_body.size(), + active_query_body); // T protocol.make_flx_ident_message(out, session_ident, m_client_file_ident, m_progress, active_query_set.version(), active_query_body); // Throws m_last_sent_flx_query_version = active_query_set.version(); } else { - logger.debug("Sending: IDENT(client_file_ident=%1, client_file_ident_salt=%2, scan_server_version=%3, " - "scan_client_version=%4, " - "latest_server_version=%5, latest_server_version_salt=%6)", + logger.debug("Sending: IDENT(client_file_ident=%1, client_file_ident_salt=%2, " + "scan_server_version=%3, scan_client_version=%4, latest_server_version=%5, " + "latest_server_version_salt=%6)", m_client_file_ident.ident, m_client_file_ident.salt, m_progress.download.server_version, m_progress.download.last_integrated_client_version, m_progress.latest_server_version.version, m_progress.latest_server_version.salt); // Throws @@ -2348,7 +2347,7 @@ Status Session::receive_download_message(const SyncProgress& progress, std::uint progress.upload.client_version, progress.upload.last_integrated_server_version, downloadable_bytes, batch_state != DownloadBatchState::MoreToCome, query_version, received_changesets.size()); // Throws - // Ignore download messages when the client detects an error. This is to prevent transforming the same bad_syntax + // Ignore download messages when the client detects an error. This is to prevent transforming the same bad // changeset over and over again. if (m_client_error) { logger.debug("Ignoring download message because the client detected an integration error"); diff --git a/src/realm/sync/noinst/protocol_codec.hpp b/src/realm/sync/noinst/protocol_codec.hpp index b108e767a2c..d398bb43276 100644 --- a/src/realm/sync/noinst/protocol_codec.hpp +++ b/src/realm/sync/noinst/protocol_codec.hpp @@ -504,6 +504,8 @@ class ClientProtocol { {"ClientResetNoRecovery", action::ClientResetNoRecovery}, {"MigrateToFLX", action::MigrateToFLX}, {"RevertToPBS", action::RevertToPBS}, + {"RefreshUser", action::RefreshUser}, + {"RefreshLocation", action::RefreshLocation}, }; if (auto action_it = mapping.find(action_string); action_it != mapping.end()) { From caa37210b85e389f9a3d1de006fafbebba43b667 Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Fri, 4 Aug 2023 15:49:14 -0400 Subject: [PATCH 21/30] fix emscripten build --- .../object-store/sync/impl/emscripten/socket_provider.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/realm/object-store/sync/impl/emscripten/socket_provider.cpp b/src/realm/object-store/sync/impl/emscripten/socket_provider.cpp index ea01d61c3c6..6df6e77b61d 100644 --- a/src/realm/object-store/sync/impl/emscripten/socket_provider.cpp +++ b/src/realm/object-store/sync/impl/emscripten/socket_provider.cpp @@ -187,8 +187,8 @@ struct EmscriptenWebSocket final : public WebSocketInterface { { auto observer = reinterpret_cast(user_data); REALM_ASSERT(event->code >= 1000 && event->code < 5000); - auto status = event->code == 1000 ? Status::OK() : Status(ErrorCodes::Error(event->code), event->reason); - observer->websocket_closed_handler(event->wasClean, std::move(status)); + observer->websocket_closed_handler(event->wasClean, static_cast(event->code), + event->reason); return EM_TRUE; } From 5833adfae5c420a72878ffabdeb0dd0982f814ad Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Sat, 5 Aug 2023 13:14:20 -0400 Subject: [PATCH 22/30] Refresh user on unauthorized error --- src/realm/sync/noinst/client_impl_base.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp index 53aad7be652..004cb404a91 100644 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ b/src/realm/sync/noinst/client_impl_base.cpp @@ -582,7 +582,7 @@ bool Connection::websocket_closed_handler(bool was_clean, WebSocketError error_c {ErrorCodes::AuthError, util::format("Websocket was closed because of an authentication issue: %1", msg)}, IsFatal{false}); - error_info.server_requests_action = ProtocolErrorInfo::Action::RefreshLocation; + error_info.server_requests_action = ProtocolErrorInfo::Action::RefreshUser; involuntary_disconnect(std::move(error_info), ConnectionTerminationReason::http_response_says_nonfatal_error); break; From a175488d4bbbdcfe92cfad46a95f29e5d4cb5fef Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Mon, 7 Aug 2023 09:14:07 -0400 Subject: [PATCH 23/30] nits --- src/realm/sync/noinst/client_impl_base.cpp | 2 +- src/realm/sync/protocol.hpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp index 004cb404a91..3c41a0504af 100644 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ b/src/realm/sync/noinst/client_impl_base.cpp @@ -1934,7 +1934,7 @@ void Session::send_ident_message() m_client_file_ident.ident, m_client_file_ident.salt, m_progress.download.server_version, m_progress.download.last_integrated_client_version, m_progress.latest_server_version.version, m_progress.latest_server_version.salt, active_query_set.version(), active_query_body.size(), - active_query_body); // T + active_query_body); // Throws protocol.make_flx_ident_message(out, session_ident, m_client_file_ident, m_progress, active_query_set.version(), active_query_body); // Throws m_last_sent_flx_query_version = active_query_set.version(); diff --git a/src/realm/sync/protocol.hpp b/src/realm/sync/protocol.hpp index 2252af3a36e..6d0e25b2b12 100644 --- a/src/realm/sync/protocol.hpp +++ b/src/realm/sync/protocol.hpp @@ -263,6 +263,8 @@ struct ProtocolErrorInfo { ClientResetNoRecovery, MigrateToFLX, RevertToPBS, + // The RefreshUser/RefreshLocation actions are currently generated internally when the + // sync websocket is closed with specific error codes. RefreshUser, RefreshLocation, }; From 952277c5d6c4987dd2269b6b438fd298bebf99b1 Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Tue, 8 Aug 2023 17:34:02 -0400 Subject: [PATCH 24/30] fixes --- src/realm/object-store/c_api/sync.cpp | 9 --------- src/realm/sync/client_base.hpp | 2 +- src/realm/sync/noinst/client_impl_base.cpp | 6 ++++-- src/realm/sync/protocol.cpp | 4 ++-- src/realm/sync/protocol.hpp | 2 +- test/test_sync.cpp | 6 +++--- 6 files changed, 11 insertions(+), 18 deletions(-) diff --git a/src/realm/object-store/c_api/sync.cpp b/src/realm/object-store/c_api/sync.cpp index a9f41928191..3919edf60ab 100644 --- a/src/realm/object-store/c_api/sync.cpp +++ b/src/realm/object-store/c_api/sync.cpp @@ -836,18 +836,9 @@ RLM_API void realm_sync_session_handle_error_for_testing(const realm_sync_sessio bool is_fatal) { REALM_ASSERT(session); -<<<<<<< HEAD SyncSession::OnlyForTesting::handle_error( *session->get(), sync::SessionErrorInfo{Status{static_cast(error_code), error_str}, !is_fatal}); -======= - realm_sync_error_code_t sync_error{static_cast(error_category), error_code, - error_message}; - std::error_code err; - sync_error_to_error_code(sync_error, &err); - SyncSession::OnlyForTesting::handle_error(*session->get(), - sync::SessionErrorInfo{Status{err, error_message}, IsFatal{is_fatal}}); ->>>>>>> feature/sync_error_unification } } // namespace realm::c_api diff --git a/src/realm/sync/client_base.hpp b/src/realm/sync/client_base.hpp index 7bc7d426e79..a2ca58136e7 100644 --- a/src/realm/sync/client_base.hpp +++ b/src/realm/sync/client_base.hpp @@ -248,7 +248,7 @@ struct ClientConfig { struct SessionErrorInfo : public ProtocolErrorInfo { SessionErrorInfo(const ProtocolErrorInfo& info) : ProtocolErrorInfo(info) - , status(protocol_error_to_status(raw_error_code, message)) + , status(protocol_error_to_status(static_cast(info.raw_error_code), message)) { } diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp index 99635429d6a..bdbd6c52281 100644 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ b/src/realm/sync/noinst/client_impl_base.cpp @@ -1520,7 +1520,8 @@ void Session::integrate_changesets(ClientReplication& repl, const SyncProgress& on_connection_state_changed( m_conn.get_state(), SessionErrorInfo{pending_error, - protocol_error_to_status(pending_error.raw_error_code, pending_error.message)}); + protocol_error_to_status(static_cast(pending_error.raw_error_code), + pending_error.message)}); } catch (...) { logger.error("Exception thrown while reporting compensating write: %1", exception_to_status()); @@ -2519,7 +2520,8 @@ Status Session::receive_error_message(const ProtocolErrorInfo& info) } m_error_message_received = true; - suspend(SessionErrorInfo{info, protocol_error_to_status(info.raw_error_code, info.message)}); + suspend(SessionErrorInfo{ + info, protocol_error_to_status(static_cast(info.raw_error_code), info.message)}); return Status::OK(); } diff --git a/src/realm/sync/protocol.cpp b/src/realm/sync/protocol.cpp index dec289cbbb8..b3f886bd914 100644 --- a/src/realm/sync/protocol.cpp +++ b/src/realm/sync/protocol.cpp @@ -135,10 +135,10 @@ std::ostream& operator<<(std::ostream& os, ProtocolError error) return os << "Unknown protocol error " << static_cast(error); } -Status protocol_error_to_status(int raw_error_code, std::string_view msg) +Status protocol_error_to_status(ProtocolError error_code, std::string_view msg) { auto err_code = [&] { - switch (ProtocolError(raw_error_code)) { + switch (error_code) { case ProtocolError::connection_closed: return ErrorCodes::ConnectionClosed; case ProtocolError::other_error: diff --git a/src/realm/sync/protocol.hpp b/src/realm/sync/protocol.hpp index 4cbb91e7c9f..c2da5b71ef6 100644 --- a/src/realm/sync/protocol.hpp +++ b/src/realm/sync/protocol.hpp @@ -364,7 +364,7 @@ enum class ProtocolError { // clang-format on }; -Status protocol_error_to_status(int raw_error_code, std::string_view msg); +Status protocol_error_to_status(ProtocolError raw_error_code, std::string_view msg); constexpr bool is_session_level_error(ProtocolError); diff --git a/test/test_sync.cpp b/test/test_sync.cpp index 53dbdc1c89e..ed4dc67c035 100644 --- a/test/test_sync.cpp +++ b/test/test_sync.cpp @@ -4489,7 +4489,7 @@ TEST(Sync_PingTimesOut) auto error_handler = [&](Status status, bool) { CHECK_EQUAL(status, ErrorCodes::ConnectionClosed); - CHECK_EQUAL(status.reason(), "Timed out waiting for PONG message"); + CHECK_EQUAL(status.reason(), "Timed out waiting for PONG response from server"); did_fail = true; fixture.stop(); }; @@ -4518,7 +4518,7 @@ TEST(Sync_ReconnectAfterPingTimeout) BowlOfStonesSemaphore bowl; auto error_handler = [&](Status status, bool) { if (CHECK_EQUAL(status, ErrorCodes::ConnectionClosed)) { - CHECK_EQUAL(status.reason(), "Timed out waiting for PONG message"); + CHECK_EQUAL(status.reason(), "Timed out waiting for PONG response from server"); bowl.add_stone(); } }; @@ -4544,7 +4544,7 @@ TEST(Sync_UrgentPingIsSent) auto error_handler = [&](Status status, bool) { CHECK_EQUAL(status, ErrorCodes::ConnectionClosed); - CHECK_EQUAL(status.reason(), "Timed out waiting for PONG message"); + CHECK_EQUAL(status.reason(), "Timed out waiting for PONG response from server"); did_fail = true; fixture.stop(); }; From a96a3f21a9ca5ce9b5660be8bc25c67782690512 Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Tue, 8 Aug 2023 21:46:00 -0400 Subject: [PATCH 25/30] handle auth errors correctly --- src/realm/object-store/sync/sync_session.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/realm/object-store/sync/sync_session.cpp b/src/realm/object-store/sync/sync_session.cpp index 7d3983a8dd6..1f808eb36a4 100644 --- a/src/realm/object-store/sync/sync_session.cpp +++ b/src/realm/object-store/sync/sync_session.cpp @@ -638,17 +638,20 @@ void SyncSession::handle_error(sync::SessionErrorInfo error) bool log_out_user = false; bool unrecognized_by_client = false; + // Can be called from within SyncSession with an AuthError to handle app-level authentication problems, + // so even if there's an error action, we need to set a fall-back to logging out the user and making + // the session inactive. + if (error.status == ErrorCodes::AuthError) { + next_state = NextStateAfterError::inactive; + log_out_user = true; + } + if (error.status == ErrorCodes::AutoClientResetFailed) { // At this point, automatic recovery has been attempted but it failed. // Fallback to a manual reset and let the user try to handle it. next_state = NextStateAfterError::inactive; delete_file = ShouldBackup::yes; } - // Can be called from within SyncSession with an AuthError to handle app-level authentication problems. - else if (error.status == ErrorCodes::AuthError) { - next_state = NextStateAfterError::inactive; - log_out_user = true; - } else if (error.server_requests_action != sync::ProtocolErrorInfo::Action::NoAction) { switch (error.server_requests_action) { case sync::ProtocolErrorInfo::Action::NoAction: From 49df1d3004644aeb9efd78d25a64be61989a10a4 Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Wed, 9 Aug 2023 09:28:10 -0400 Subject: [PATCH 26/30] fixups --- src/realm/impl/simulated_failure.hpp | 1 - src/realm/object-store/c_api/sync.cpp | 7 +- src/realm/sync/client.cpp | 1 + src/realm/sync/noinst/client_history_impl.cpp | 1 + src/realm/sync/noinst/client_impl_base.cpp | 53 +++++----- src/realm/sync/noinst/client_impl_base.hpp | 3 - src/realm/sync/noinst/protocol_codec.hpp | 12 --- src/realm/sync/protocol.cpp | 97 ++++++++++--------- 8 files changed, 79 insertions(+), 96 deletions(-) diff --git a/src/realm/impl/simulated_failure.hpp b/src/realm/impl/simulated_failure.hpp index a506d4b2303..0638a557011 100644 --- a/src/realm/impl/simulated_failure.hpp +++ b/src/realm/impl/simulated_failure.hpp @@ -24,7 +24,6 @@ #include #include -#include #ifdef REALM_DEBUG #define REALM_ENABLE_SIMULATED_FAILURE diff --git a/src/realm/object-store/c_api/sync.cpp b/src/realm/object-store/c_api/sync.cpp index 3919edf60ab..bc862097794 100644 --- a/src/realm/object-store/c_api/sync.cpp +++ b/src/realm/object-store/c_api/sync.cpp @@ -257,10 +257,7 @@ RLM_API void realm_sync_config_set_error_handler(realm_sync_config_t* config, re auto c_error = realm_sync_error_t(); std::string error_code_message; - const auto& status = error.status; - c_error.status.message = status.reason().c_str(); - c_error.status.error = static_cast(status.code()); - c_error.status.categories = ErrorCodes::error_categories(status.code()).value(); + c_error.status = to_capi(error.status); c_error.is_fatal = error.is_fatal; c_error.is_unrecognized_by_client = error.is_unrecognized_by_client; c_error.is_client_reset_requested = error.is_client_reset_requested(); @@ -801,7 +798,6 @@ RLM_API void realm_sync_session_wait_for_download_completion(realm_sync_session_ util::UniqueFunction cb = [done, userdata = SharedUserdata(userdata, FreeUserdata(userdata_free))](Status s) { if (!s.is_ok()) { - std::string error_code_message; realm_error_t error = to_capi(s); done(userdata.get(), &error); } @@ -820,7 +816,6 @@ RLM_API void realm_sync_session_wait_for_upload_completion(realm_sync_session_t* util::UniqueFunction cb = [done, userdata = SharedUserdata(userdata, FreeUserdata(userdata_free))](Status s) { if (!s.is_ok()) { - std::string error_code_message; realm_error_t error = to_capi(s); done(userdata.get(), &error); } diff --git a/src/realm/sync/client.cpp b/src/realm/sync/client.cpp index 81bc0977690..cd3f23884f7 100644 --- a/src/realm/sync/client.cpp +++ b/src/realm/sync/client.cpp @@ -27,6 +27,7 @@ namespace sync { namespace { using namespace realm::util; + // clang-format off using SessionImpl = ClientImpl::Session; using SyncTransactReporter = ClientHistory::SyncTransactReporter; diff --git a/src/realm/sync/noinst/client_history_impl.cpp b/src/realm/sync/noinst/client_history_impl.cpp index 62ba726b524..b227a367b06 100644 --- a/src/realm/sync/noinst/client_history_impl.cpp +++ b/src/realm/sync/noinst/client_history_impl.cpp @@ -803,6 +803,7 @@ void ClientHistory::update_sync_progress(const SyncProgress& progress, const std { Array& root = m_arrays->root; + // Progress must never decrease if (auto current = version_type(root.get_as_ref_or_tagged(s_progress_latest_server_version_iip).get_as_int()); progress.latest_server_version.version < current) { throw IntegrationException(ErrorCodes::SyncProtocolInvariantFailed, diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp index bdbd6c52281..9b99a08dba3 100644 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ b/src/realm/sync/noinst/client_impl_base.cpp @@ -1291,7 +1291,6 @@ void Connection::receive_error_message(const ProtocolErrorInfo& info, session_id close_due_to_server_side_error(error_code, info); // Throws return; } - close_due_to_protocol_error( {ErrorCodes::SyncProtocolInvariantFailed, util::format("Received ERROR message with a non-connection-level error code %1 without a session ident", @@ -1570,8 +1569,7 @@ void Session::on_changesets_integrated(version_type client_version, const SyncPr do_recognize_sync_version(client_version); // Allows upload process to resume check_for_download_completion(); // Throws - // If the client migrated from PBS to FLX, create subscriptions when new tables are received from - // server. + // If the client migrated from PBS to FLX, create subscriptions when new tables are received from server. if (auto migration_store = get_migration_store(); migration_store && m_is_flx_sync_session) { auto& flx_subscription_store = *get_flx_subscription_store(); get_migration_store()->create_subscriptions(flx_subscription_store); @@ -1614,9 +1612,9 @@ void Session::activate() // DB. If it did, then the fresh DB would stay alive for the duration of this sync session // and we want to clean it up once the reset is finished. Additionally, the fresh copy will // be set to a new copy on every reset so there is no reason to keep a reference to it. - // The modification to the client reset config happens via - // std::move(client_reset_config->fresh_copy). If the client reset config were a `const &` then - // this std::move would create another strong reference which we don't want to happen. + // The modification to the client reset config happens via std::move(client_reset_config->fresh_copy). + // If the client reset config were a `const &` then this std::move would create another strong + // reference which we don't want to happen. util::Optional& client_reset_config = get_client_reset_config(); bool file_exists = util::File::exists(get_realm_path()); @@ -1854,21 +1852,19 @@ void Session::send_bind_message() if (!bind_json_data.empty()) { json_data_dump = bind_json_data.dump(); } - logger.debug("Sending: BIND(session_ident=%1, need_client_file_ident=%2, is_subserver=%3, " - "json_data=\"%4\")", - session_ident, need_client_file_ident, is_subserver, json_data_dump); + logger.debug( + "Sending: BIND(session_ident=%1, need_client_file_ident=%2, is_subserver=%3, json_data=\"%4\")", + session_ident, need_client_file_ident, is_subserver, json_data_dump); } protocol.make_flx_bind_message(protocol_version, out, session_ident, bind_json_data, empty_access_token, - need_client_file_ident, - is_subserver); // Throws + need_client_file_ident, is_subserver); // Throws } else { std::string server_path = get_virt_path(); logger.debug("Sending: BIND(session_ident=%1, need_client_file_ident=%2, is_subserver=%3, server_path=%4)", session_ident, need_client_file_ident, is_subserver, server_path); protocol.make_pbs_bind_message(protocol_version, out, session_ident, server_path, empty_access_token, - need_client_file_ident, - is_subserver); // Throws + need_client_file_ident, is_subserver); // Throws } m_conn.initiate_write_message(out, this); // Throws @@ -2315,8 +2311,8 @@ Status Session::receive_download_message(const SyncProgress& progress, std::uint progress.upload.client_version, progress.upload.last_integrated_server_version, downloadable_bytes, batch_state != DownloadBatchState::MoreToCome, query_version, received_changesets.size()); // Throws - // Ignore download messages when the client detects an error. This is to prevent transforming the same - // bad changeset over and over again. + // Ignore download messages when the client detects an error. This is to prevent transforming the same bad + // changeset over and over again. if (m_client_error) { logger.debug("Ignoring download message because the client detected an integration error"); return Status::OK(); @@ -2334,8 +2330,8 @@ Status Session::receive_download_message(const SyncProgress& progress, std::uint version_type server_version = m_progress.download.server_version; version_type last_integrated_client_version = m_progress.download.last_integrated_client_version; for (const Transformer::RemoteChangeset& changeset : received_changesets) { - // Check that per-changeset server version is strictly increasing, except in FLX sync where the - // server version must be increasing, but can stay the same during bootstraps. + // Check that per-changeset server version is strictly increasing, except in FLX sync where the server + // version must be increasing, but can stay the same during bootstraps. bool good_server_version = m_is_flx_sync_session ? (changeset.remote_version >= server_version) : (changeset.remote_version > server_version); // Each server version cannot be greater than the one in the header of the download message. @@ -2382,8 +2378,7 @@ Status Session::receive_download_message(const SyncProgress& progress, std::uint return Status::OK(); } - initiate_integrate_changesets(downloadable_bytes, batch_state, progress, - received_changesets); // Throws + initiate_integrate_changesets(downloadable_bytes, batch_state, progress, received_changesets); // Throws hook_action = call_debug_hook(SyncClientHookEvent::DownloadMessageIntegrated, progress, query_version, batch_state, received_changesets.size()); @@ -2392,8 +2387,8 @@ Status Session::receive_download_message(const SyncProgress& progress, std::uint } REALM_ASSERT_EX(hook_action == SyncClientHookAction::NoAction, hook_action); - // When we receive a DOWNLOAD message successfully, we can clear the backoff timer value used to - // reconnect after a retryable session error. + // When we receive a DOWNLOAD message successfully, we can clear the backoff timer value used to reconnect + // after a retryable session error. clear_resumption_delay_state(); return Status::OK(); } @@ -2485,8 +2480,11 @@ Status Session::receive_error_message(const ProtocolErrorInfo& info) return {ErrorCodes::SyncProtocolInvariantFailed, "Received ERROR message when it was not legal"}; } - bool known_error_code = bool(get_protocol_error_message(info.raw_error_code)); - if (REALM_UNLIKELY(!known_error_code) && info.server_requests_action != ProtocolErrorInfo::Action::NoAction) { + auto status = protocol_error_to_status(static_cast(info.raw_error_code), info.message); + // If the server has requested an action but sent an invalid error code we can still process the error and pass an + // UnknownError status to the user. This lets us add error codes on the server side without strictly having to + // bump the protocol version to avoid crashing older clients. + if (status == ErrorCodes::UnknownError && info.server_requests_action != ProtocolErrorInfo::Action::NoAction) { return { ErrorCodes::SyncProtocolInvariantFailed, util::format("Received ERROR message with unknown error code %1 and no action.", info.raw_error_code)}; @@ -2508,9 +2506,9 @@ Status Session::receive_error_message(const ProtocolErrorInfo& info) } } - // For compensating write errors, we need to defer raising them to the SDK until after the server - // version containing the compensating write has appeared in a download message. - if (ProtocolError(info.raw_error_code) == ProtocolError::compensating_write) { + // For compensating write errors, we need to defer raising them to the SDK until after the server version + // containing the compensating write has appeared in a download message. + if (status == ErrorCodes::SyncCompensatingWrite) { // If the client is not active, the compensating writes will not be processed now, but will be // sent again the next time the client connects if (m_state == Active) { @@ -2520,8 +2518,7 @@ Status Session::receive_error_message(const ProtocolErrorInfo& info) } m_error_message_received = true; - suspend(SessionErrorInfo{ - info, protocol_error_to_status(static_cast(info.raw_error_code), info.message)}); + suspend(SessionErrorInfo{info, std::move(status)}); return Status::OK(); } diff --git a/src/realm/sync/noinst/client_impl_base.hpp b/src/realm/sync/noinst/client_impl_base.hpp index 4a0b5e2ab25..07aa1541f77 100644 --- a/src/realm/sync/noinst/client_impl_base.hpp +++ b/src/realm/sync/noinst/client_impl_base.hpp @@ -561,10 +561,7 @@ class ClientImpl::Connection { void initiate_disconnect_wait(); void handle_disconnect_wait(Status status); void close_due_to_protocol_error(Status status); - void close_due_to_client_side_error(Status, IsFatal is_fatal, ConnectionTerminationReason reason); - void read_or_write_error(std::error_code ec, std::string_view msg); - void close_due_to_transient_error(Status status, ConnectionTerminationReason reason); void close_due_to_server_side_error(ProtocolError, const ProtocolErrorInfo& info); void involuntary_disconnect(const SessionErrorInfo& info, ConnectionTerminationReason reason); diff --git a/src/realm/sync/noinst/protocol_codec.hpp b/src/realm/sync/noinst/protocol_codec.hpp index e12ba7e54ec..e307881dafe 100644 --- a/src/realm/sync/noinst/protocol_codec.hpp +++ b/src/realm/sync/noinst/protocol_codec.hpp @@ -519,18 +519,6 @@ class ServerProtocol { using OutputBuffer = util::ResettableExpandableBufferOutputStream; - // FIXME: No need to explicitly assign numbers to these - enum class Error { - // clang-format off - unknown_message = 101, // Unknown type of input message - bad_syntax = 102, // Bad syntax in input message head - limits_exceeded = 103, // Limits exceeded in input message - bad_decompression = 104, // Error in decompression (UPLOAD) - bad_changeset_header_syntax = 105, // Bad syntax in changeset header (UPLOAD) - bad_changeset_size = 106, // Changeset size doesn't fit in message (UPLOAD) - // clang-format on - }; - // Messages sent by the server to the client void make_ident_message(int protocol_version, OutputBuffer&, session_ident_type session_ident, diff --git a/src/realm/sync/protocol.cpp b/src/realm/sync/protocol.cpp index b3f886bd914..b1c214fe784 100644 --- a/src/realm/sync/protocol.cpp +++ b/src/realm/sync/protocol.cpp @@ -137,36 +137,34 @@ std::ostream& operator<<(std::ostream& os, ProtocolError error) Status protocol_error_to_status(ProtocolError error_code, std::string_view msg) { - auto err_code = [&] { + auto translated_error_code = [&] { switch (error_code) { case ProtocolError::connection_closed: return ErrorCodes::ConnectionClosed; case ProtocolError::other_error: return ErrorCodes::RuntimeError; case ProtocolError::unknown_message: - return ErrorCodes::SyncProtocolInvariantFailed; + [[fallthrough]]; case ProtocolError::bad_syntax: - return ErrorCodes::SyncProtocolInvariantFailed; - case ProtocolError::limits_exceeded: - REALM_UNREACHABLE(); + [[fallthrough]]; case ProtocolError::wrong_protocol_version: - return ErrorCodes::SyncProtocolNegotiationFailed; + [[fallthrough]]; case ProtocolError::bad_session_ident: - return ErrorCodes::SyncProtocolInvariantFailed; + [[fallthrough]]; case ProtocolError::reuse_of_session_ident: - return ErrorCodes::SyncProtocolInvariantFailed; + [[fallthrough]]; case ProtocolError::bound_in_other_session: - return ErrorCodes::SyncProtocolInvariantFailed; + [[fallthrough]]; + case ProtocolError::bad_changeset_header_syntax: + [[fallthrough]]; + case ProtocolError::bad_changeset_size: + [[fallthrough]]; case ProtocolError::bad_message_order: return ErrorCodes::SyncProtocolInvariantFailed; case ProtocolError::bad_decompression: return ErrorCodes::RuntimeError; - case ProtocolError::bad_changeset_header_syntax: - return ErrorCodes::SyncProtocolInvariantFailed; - case ProtocolError::bad_changeset_size: - return ErrorCodes::SyncProtocolInvariantFailed; case ProtocolError::switch_to_flx_sync: - return ErrorCodes::WrongSyncType; + [[fallthrough]]; case ProtocolError::switch_to_pbs: return ErrorCodes::WrongSyncType; @@ -174,50 +172,28 @@ Status protocol_error_to_status(ProtocolError error_code, std::string_view msg) return ErrorCodes::ConnectionClosed; case ProtocolError::other_session_error: return ErrorCodes::RuntimeError; - case ProtocolError::token_expired: - REALM_UNREACHABLE(); - case ProtocolError::bad_authentication: - REALM_UNREACHABLE(); case ProtocolError::illegal_realm_path: return ErrorCodes::BadSyncPartitionValue; - case ProtocolError::no_such_realm: - REALM_UNREACHABLE(); case ProtocolError::permission_denied: return ErrorCodes::SyncPermissionDenied; - case ProtocolError::bad_server_file_ident: - REALM_UNREACHABLE(); case ProtocolError::bad_client_file_ident: - return ErrorCodes::SyncClientResetRequired; + [[fallthrough]]; case ProtocolError::bad_server_version: - return ErrorCodes::SyncClientResetRequired; + [[fallthrough]]; case ProtocolError::bad_client_version: - return ErrorCodes::SyncClientResetRequired; + [[fallthrough]]; case ProtocolError::diverging_histories: + [[fallthrough]]; + case ProtocolError::client_file_expired: + [[fallthrough]]; + case ProtocolError::bad_client_file: return ErrorCodes::SyncClientResetRequired; case ProtocolError::bad_changeset: return ErrorCodes::BadChangeset; - case ProtocolError::partial_sync_disabled: - REALM_UNREACHABLE(); - case ProtocolError::unsupported_session_feature: - REALM_UNREACHABLE(); case ProtocolError::bad_origin_file_ident: return ErrorCodes::SyncProtocolInvariantFailed; - case ProtocolError::bad_client_file: - return ErrorCodes::SyncClientResetRequired; - case ProtocolError::server_file_deleted: - REALM_UNREACHABLE(); - case ProtocolError::client_file_blacklisted: - REALM_UNREACHABLE(); - case ProtocolError::user_blacklisted: - REALM_UNREACHABLE(); - case ProtocolError::transact_before_upload: - REALM_UNREACHABLE(); - case ProtocolError::client_file_expired: - return ErrorCodes::SyncClientResetRequired; case ProtocolError::user_mismatch: return ErrorCodes::SyncUserMismatch; - case ProtocolError::too_many_sessions: - REALM_UNREACHABLE(); case ProtocolError::invalid_schema_change: return ErrorCodes::InvalidSchemaChange; case ProtocolError::bad_query: @@ -232,17 +208,46 @@ Status protocol_error_to_status(ProtocolError error_code, std::string_view msg) return ErrorCodes::SyncWriteNotAllowed; case ProtocolError::compensating_write: return ErrorCodes::SyncCompensatingWrite; - case ProtocolError::migrate_to_flx: - return ErrorCodes::WrongSyncType; case ProtocolError::bad_progress: return ErrorCodes::SyncProtocolInvariantFailed; + case ProtocolError::migrate_to_flx: + [[fallthrough]]; case ProtocolError::revert_to_pbs: return ErrorCodes::WrongSyncType; + + case ProtocolError::limits_exceeded: + [[fallthrough]]; + case ProtocolError::token_expired: + [[fallthrough]]; + case ProtocolError::bad_authentication: + [[fallthrough]]; + case ProtocolError::no_such_realm: + [[fallthrough]]; + case ProtocolError::bad_server_file_ident: + [[fallthrough]]; + case ProtocolError::partial_sync_disabled: + [[fallthrough]]; + case ProtocolError::unsupported_session_feature: + [[fallthrough]]; + case ProtocolError::too_many_sessions: + [[fallthrough]]; + case ProtocolError::server_file_deleted: + [[fallthrough]]; + case ProtocolError::client_file_blacklisted: + [[fallthrough]]; + case ProtocolError::user_blacklisted: + [[fallthrough]]; + case ProtocolError::transact_before_upload: + REALM_UNREACHABLE(); } return ErrorCodes::UnknownError; }(); - return {err_code, msg}; + if (translated_error_code == ErrorCodes::UnknownError) { + return {ErrorCodes::UnknownError, + util::format("Unknown sync protocol error code %1: %2", static_cast(error_code), msg)}; + } + return {translated_error_code, msg}; } } // namespace realm::sync From ec5d2920ef61d4fec1464aa889a22c3f819c46c3 Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Thu, 10 Aug 2023 10:06:16 -0400 Subject: [PATCH 27/30] fixes from PR --- src/realm/object-store/c_api/conversion.hpp | 2 +- src/realm/object-store/sync/sync_session.cpp | 12 ++++-------- src/realm/sync/noinst/client_impl_base.cpp | 9 ++++----- src/realm/sync/noinst/protocol_codec.hpp | 1 + src/realm/sync/protocol.hpp | 5 ++++- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/realm/object-store/c_api/conversion.hpp b/src/realm/object-store/c_api/conversion.hpp index c3812c6be7c..ac4bcd354f4 100644 --- a/src/realm/object-store/c_api/conversion.hpp +++ b/src/realm/object-store/c_api/conversion.hpp @@ -485,7 +485,7 @@ static inline realm_version_id_t to_capi(const VersionID& v) return version_id; } -static inline realm_error_t to_capi(const Status s) +static inline realm_error_t to_capi(const Status& s) { realm_error_t err; err.error = static_cast(s.code()); diff --git a/src/realm/object-store/sync/sync_session.cpp b/src/realm/object-store/sync/sync_session.cpp index 1f808eb36a4..7152bda9b2d 100644 --- a/src/realm/object-store/sync/sync_session.cpp +++ b/src/realm/object-store/sync/sync_session.cpp @@ -638,14 +638,6 @@ void SyncSession::handle_error(sync::SessionErrorInfo error) bool log_out_user = false; bool unrecognized_by_client = false; - // Can be called from within SyncSession with an AuthError to handle app-level authentication problems, - // so even if there's an error action, we need to set a fall-back to logging out the user and making - // the session inactive. - if (error.status == ErrorCodes::AuthError) { - next_state = NextStateAfterError::inactive; - log_out_user = true; - } - if (error.status == ErrorCodes::AutoClientResetFailed) { // At this point, automatic recovery has been attempted but it failed. // Fallback to a manual reset and let the user try to handle it. @@ -723,6 +715,10 @@ void SyncSession::handle_error(sync::SessionErrorInfo error) return; } break; + case sync::ProtocolErrorInfo::Action::LogOutUser: + next_state = NextStateAfterError::inactive; + log_out_user = true; + break; } } else { diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp index 9b99a08dba3..23d4ed002f9 100644 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ b/src/realm/sync/noinst/client_impl_base.cpp @@ -577,7 +577,9 @@ bool Connection::websocket_closed_handler(bool was_clean, WebSocketError error_c break; } case WebSocketError::websocket_forbidden: { - involuntary_disconnect(SessionErrorInfo({ErrorCodes::AuthError, msg}, IsFatal{true}), + SessionErrorInfo error_info({ErrorCodes::AuthError, msg}, IsFatal{true}); + error_info.server_requests_action = ProtocolErrorInfo::Action::LogOutUser; + involuntary_disconnect(std::move(error_info), ConnectionTerminationReason::http_response_says_fatal_error); break; } @@ -2481,10 +2483,7 @@ Status Session::receive_error_message(const ProtocolErrorInfo& info) } auto status = protocol_error_to_status(static_cast(info.raw_error_code), info.message); - // If the server has requested an action but sent an invalid error code we can still process the error and pass an - // UnknownError status to the user. This lets us add error codes on the server side without strictly having to - // bump the protocol version to avoid crashing older clients. - if (status == ErrorCodes::UnknownError && info.server_requests_action != ProtocolErrorInfo::Action::NoAction) { + if (status == ErrorCodes::UnknownError) { return { ErrorCodes::SyncProtocolInvariantFailed, util::format("Received ERROR message with unknown error code %1 and no action.", info.raw_error_code)}; diff --git a/src/realm/sync/noinst/protocol_codec.hpp b/src/realm/sync/noinst/protocol_codec.hpp index e307881dafe..6d8a6d4c85e 100644 --- a/src/realm/sync/noinst/protocol_codec.hpp +++ b/src/realm/sync/noinst/protocol_codec.hpp @@ -482,6 +482,7 @@ class ClientProtocol { {"RevertToPBS", action::RevertToPBS}, {"RefreshUser", action::RefreshUser}, {"RefreshLocation", action::RefreshLocation}, + {"LogOutUser", action::LogOutUser}, }; if (auto action_it = mapping.find(action_string); action_it != mapping.end()) { diff --git a/src/realm/sync/protocol.hpp b/src/realm/sync/protocol.hpp index c2da5b71ef6..6a1923fac3c 100644 --- a/src/realm/sync/protocol.hpp +++ b/src/realm/sync/protocol.hpp @@ -263,10 +263,11 @@ struct ProtocolErrorInfo { ClientResetNoRecovery, MigrateToFLX, RevertToPBS, - // The RefreshUser/RefreshLocation actions are currently generated internally when the + // The RefreshUser/RefreshLocation/LogOutUser actions are currently generated internally when the // sync websocket is closed with specific error codes. RefreshUser, RefreshLocation, + LogOutUser, }; ProtocolErrorInfo() = default; @@ -437,6 +438,8 @@ inline std::ostream& operator<<(std::ostream& o, ProtocolErrorInfo::Action actio return o << "RefreshUser"; case ProtocolErrorInfo::Action::RefreshLocation: return o << "RefreshLocation"; + case ProtocolErrorInfo::Action::LogOutUser: + return o << "LogOutUser"; } return o << "Invalid error action: " << int64_t(action); } From f2d5c8f465021871b6014fa1125074054af8229f Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Thu, 10 Aug 2023 11:51:37 -0400 Subject: [PATCH 28/30] update error message --- src/realm/sync/noinst/client_impl_base.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp index 23d4ed002f9..a9af5ebf2a0 100644 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ b/src/realm/sync/noinst/client_impl_base.cpp @@ -2484,9 +2484,8 @@ Status Session::receive_error_message(const ProtocolErrorInfo& info) auto status = protocol_error_to_status(static_cast(info.raw_error_code), info.message); if (status == ErrorCodes::UnknownError) { - return { - ErrorCodes::SyncProtocolInvariantFailed, - util::format("Received ERROR message with unknown error code %1 and no action.", info.raw_error_code)}; + return {ErrorCodes::SyncProtocolInvariantFailed, + util::format("Received ERROR message with unknown error code %1", info.raw_error_code)}; } else if (auto error_code = ProtocolError(info.raw_error_code); REALM_UNLIKELY(!is_session_level_error(error_code))) { From d164b3ffec68a1cd47bcc3c5cab445040e01f5a5 Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Thu, 10 Aug 2023 12:04:11 -0400 Subject: [PATCH 29/30] remove duplicate --- src/realm/sync/protocol.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/realm/sync/protocol.hpp b/src/realm/sync/protocol.hpp index 6a1923fac3c..0df1a5cbf1d 100644 --- a/src/realm/sync/protocol.hpp +++ b/src/realm/sync/protocol.hpp @@ -374,8 +374,6 @@ constexpr bool is_session_level_error(ProtocolError); const char* get_protocol_error_message(int error_code) noexcept; std::ostream& operator<<(std::ostream&, ProtocolError protocol_error); -Status protocol_error_to_status(ProtocolError error_code, std::string_view msg); - // Implementation inline bool is_consistent(DownloadCursor dc) noexcept From 3e363558e0dd3ea21bf05c4af844ea9c55ca4836 Mon Sep 17 00:00:00 2001 From: Jonathan Reams Date: Fri, 11 Aug 2023 10:18:05 -0400 Subject: [PATCH 30/30] changelog --- CHANGELOG.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd5fa2f2f8f..b381b8ccf4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,12 @@ ### Breaking changes * The `WebSocketObserver` interface in the sync `SocketProvider` API now takes a `WebSocketError` enum/`std::string_view` for the `websocket_closed_handler()` instead of a `Status`. Implementers of platform networking should make sure all their error handling is expressed in terms of the WebSocketError enum. ([PR #6859](https://github.com/realm/realm-core/pull/6859)) +* `Status` no longer holds a `std::error_code` for `SystemError`'s ([PR #6869](https://github.com/realm/realm-core/pull/6869)) +* C API no longer has a special type for sync error codes. Instead sync errors codes are converted to `realm_error_t` ([PR #6869](https://github.com/realm/realm-core/pull/6869)) +* WebSocket specific error codes are no longer in the ErrorCodes enum or C API. ([PR #6869](https://github.com/realm/realm-core/pull/6869)) +* `ProtocolError` is no longer a `std::error_code` enum and is no longer directly exposed by the sync error API ([PR #6869](https://github.com/realm/realm-core/pull/6869)) +* The ClientError enum/`std::error_code` in the sync client has been removed in favor of a simplified error set using Status/ErrorCodes ([PR #6846](https://github.com/realm/realm-core/pull/6846)). +* SyncError now contains a Status to hold the error information from the sync client instead of a `std::error_code`/`std::string` ([PR #6824](https://github.com/realm/realm-core/pull/6824)). ### Compatibility * Fileformat: Generates files with format v23. Reads and automatically upgrade from fileformat v5. @@ -18,8 +24,6 @@ ### Internals * Timestamp objects can now only be created from a system clock timepoint. ([#6112](https://github.com/realm/realm-core/issues/6112)) -* SyncError now contains a Status to hold the error information from the sync client instead of a `std::error_code`/`std::string` ([PR #6824](https://github.com/realm/realm-core/pull/6824)). -* The ClientError enum/`std::error_code` in the sync client has been removed in favor of a simplified error set using Status/ErrorCodes ([PR #6846](https://github.com/realm/realm-core/pull/6846)). ----------------------------------------------