-
Notifications
You must be signed in to change notification settings - Fork 169
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RCORE-2060 Enabling 'cancel_waits_on_nonfatal_error' does not cancel waits during location update while offline #7528
Merged
michael-wb
merged 25 commits into
prepare-14.6.0
from
mwb/fix-cancel-waits-on-nonfatal-error
Apr 26, 2024
Merged
Changes from 23 commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
2990263
added tests using cancel_waits_on_nonfatal_error and fix operation du…
b5d2dc5
Merge branch 'master' of github.com:realm/realm-core into mwb/fix-can…
c060daf
Updated changelog and updated comments/debug statements
91a54f1
fix swift build and test and tsan errors
5650107
Update from review
ea76f65
Fixed TSAN failures
0776dd8
Merge branch 'master' of github.com:realm/realm-core into mwb/fix-can…
7298787
Updates from review
5ac829b
Fixed lint failure
b3bb528
Updates from review
3bad95e
Merge branch 'master' of github.com:realm/realm-core into mwb/fix-can…
3c158a9
More updates from review
6e5f0a4
Additional updates from review
3f29c30
Added test to replicate swift autoopen feature
a6d55e9
Merge branch 'master' of github.com:realm/realm-core into mwb/fix-can…
1ef3639
Updated test
52a1d54
Merge branch 'master' of github.com:realm/realm-core into mwb/fix-can…
669d09c
Fixed swift build issue
b83a346
Merge branch 'master' of github.com:realm/realm-core into mwb/fix-can…
b2e7406
Fixed test failures
5830b46
Merge branch 'master' of github.com:realm/realm-core into mwb/fix-can…
c1a25c0
removed an unused function
dce8409
Merge branch 'master' of github.com:realm/realm-core into mwb/fix-can…
a34d5f6
Fixed debug message
2e0a68f
Updates from review
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -842,6 +842,7 @@ void App::log_out(const std::shared_ptr<User>& user, SyncUser::State new_state, | |
return; | ||
} | ||
|
||
log_debug("App: log_out(%1)", user->user_id()); | ||
auto request = | ||
make_request(HttpMethod::del, url_for_path("/auth/session"), user, RequestTokenType::RefreshToken, ""); | ||
|
||
|
@@ -1284,14 +1285,17 @@ void App::refresh_access_token(const std::shared_ptr<User>& user, bool update_lo | |
return; | ||
} | ||
|
||
log_debug("App: refresh_access_token: email: %1 %2", user->user_profile().email(), | ||
update_location ? "(updating location)" : ""); | ||
log_debug("App: refresh_access_token: user-id: %1%2", user->user_id(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: user_id |
||
update_location ? " (updating location)" : ""); | ||
|
||
// If update_location is set, force the location info to be updated before sending the request | ||
do_request( | ||
make_request(HttpMethod::post, url_for_path("/auth/session"), user, RequestTokenType::RefreshToken, ""), | ||
[completion = std::move(completion), self = shared_from_this(), user](auto&&, const Response& response) { | ||
if (auto error = AppUtils::check_for_errors(response)) { | ||
self->log_error("App: refresh_access_token: %1 -> %2 ERROR: %3", user->user_id(), | ||
response.http_status_code, error->what()); | ||
|
||
return completion(std::move(error)); | ||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -45,14 +45,19 @@ | |
|
||
#if REALM_ENABLE_SYNC | ||
#include <util/sync/flx_sync_harness.hpp> | ||
#include <util/sync/sync_test_utils.hpp> | ||
#include <util/test_file.hpp> | ||
#ifdef REALM_ENABLE_AUTH_TESTS | ||
#include <util/sync/baas_admin_api.hpp> | ||
#endif // REALM_ENABLE_AUTH_TESTS | ||
|
||
#include <realm/object-store/sync/async_open_task.hpp> | ||
#include <realm/object-store/sync/impl/app_metadata.hpp> | ||
#include <realm/object-store/sync/sync_session.hpp> | ||
|
||
#include <realm/sync/noinst/client_history_impl.hpp> | ||
#include <realm/sync/subscriptions.hpp> | ||
#endif | ||
#endif // REALM_ENABLE_SYNC | ||
|
||
#include <catch2/catch_all.hpp> | ||
#include <catch2/matchers/catch_matchers_string.hpp> | ||
|
@@ -62,7 +67,7 @@ | |
#include <array> | ||
#if REALM_HAVE_UV | ||
#include <uv.h> | ||
#endif | ||
#endif // REALM_HAVE_UV | ||
|
||
namespace realm { | ||
class TestHelper { | ||
|
@@ -1215,6 +1220,148 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { | |
REQUIRE(got_error); | ||
} | ||
|
||
#if REALM_APP_SERVICES | ||
|
||
SECTION("waiters are cancelled if cancel_waits_on_nonfatal_error") { | ||
auto logger = util::Logger::get_default_logger(); | ||
auto transport = std::make_shared<HookedTransport<UnitTestTransport>>(); | ||
auto socket_provider = std::make_shared<HookedSocketProvider>(logger, "some user agent"); | ||
enum TestMode { expired_at_start, expired_by_websocket, websocket_fails }; | ||
enum FailureMode { location_fails, token_fails, token_not_authorized }; | ||
auto txt_test_mode = [](TestMode mode) { | ||
switch (mode) { | ||
case TestMode::expired_at_start: | ||
return "access token expired when realm is opened"; | ||
case TestMode::expired_by_websocket: | ||
return "access token expired by websocket"; | ||
case TestMode::websocket_fails: | ||
return "websocket returns connection failed"; | ||
default: | ||
return "Unknown TestMode"; | ||
} | ||
}; | ||
auto txt_failure_mode = [](FailureMode mode) { | ||
switch (mode) { | ||
case FailureMode::location_fails: | ||
return "location update fails"; | ||
case FailureMode::token_fails: | ||
return "access token refresh fails"; | ||
case FailureMode::token_not_authorized: | ||
return "websocket connect not authorized"; | ||
default: | ||
return "Unknown FailureMode"; | ||
} | ||
}; | ||
|
||
app::AppConfig app_config; | ||
set_app_config_defaults(app_config, transport); | ||
app_config.sync_client_config.socket_provider = socket_provider; | ||
app_config.base_file_path = util::make_temp_dir(); | ||
app_config.metadata_mode = app::AppConfig::MetadataMode::NoEncryption; | ||
|
||
auto the_app = app::App::get_app(app::App::CacheMode::Disabled, app_config); | ||
create_user_and_log_in(the_app); | ||
auto user = the_app->current_user(); | ||
// User should be logged in at this point | ||
michael-wb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
REQUIRE(user->is_logged_in()); | ||
|
||
bool not_authorized = false; | ||
bool token_refresh_called = false; | ||
bool location_refresh_called = false; | ||
|
||
TestMode test_mode = GENERATE(expired_at_start, expired_by_websocket, websocket_fails); | ||
FailureMode failure = GENERATE(location_fails, token_fails, token_not_authorized); | ||
|
||
DYNAMIC_SECTION(txt_test_mode(test_mode) << " - " << txt_failure_mode(failure)) { | ||
logger->info("TEST: %1 - %2", txt_test_mode(test_mode), txt_failure_mode(failure)); | ||
if (test_mode == TestMode::expired_at_start) { | ||
// invalidate the user's cached access token | ||
auto app_user = the_app->current_user(); | ||
app_user->update_data_for_testing([&](app::UserData& data) { | ||
data.access_token = RealmJWT(expired_token); | ||
}); | ||
} | ||
else if (test_mode == TestMode::expired_by_websocket) { | ||
// tell websocket to return not authorized to refresh access token | ||
not_authorized = true; | ||
} | ||
} | ||
|
||
the_app.reset(); | ||
|
||
auto err_handler = [](std::shared_ptr<SyncSession> session, SyncError error) { | ||
auto logger = util::Logger::get_default_logger(); | ||
logger->debug("The sync error handler caught an error: '%1' for '%2'", error.status, session->path()); | ||
// Ignore connection failed non-fatal errors and check for access token refresh unauthorized fatal errors | ||
if (error.status.code() == ErrorCodes::SyncConnectFailed) { | ||
REQUIRE_FALSE(error.is_fatal); | ||
return; | ||
} | ||
// If it's not SyncConnectFailed, then it should be AuthError | ||
REQUIRE(error.status.code() == ErrorCodes::AuthError); | ||
REQUIRE(error.is_fatal); | ||
}; | ||
|
||
transport->request_hook = [&](const app::Request& req) -> std::optional<app::Response> { | ||
static constexpr int CURLE_OPERATION_TIMEDOUT = 28; | ||
std::lock_guard<std::mutex> lock(mutex); | ||
if (req.url.find("/auth/session") != std::string::npos) { | ||
token_refresh_called = true; | ||
if (failure == FailureMode::token_not_authorized) { | ||
return app::Response{403, 0, {}, "403 not authorized"}; | ||
} | ||
if (failure == FailureMode::token_fails) { | ||
return app::Response{0, CURLE_OPERATION_TIMEDOUT, {}, "Operation timed out"}; | ||
} | ||
} | ||
else if (req.url.find("/location") != std::string::npos) { | ||
location_refresh_called = true; | ||
if (failure == FailureMode::location_fails) { | ||
// Fake "offline/request timed out" custom error response | ||
return app::Response{0, CURLE_OPERATION_TIMEDOUT, {}, "Operation timed out"}; | ||
} | ||
} | ||
return std::nullopt; | ||
}; | ||
|
||
socket_provider->websocket_connect_func = [&]() -> std::optional<SocketProviderError> { | ||
if (not_authorized) { | ||
not_authorized = false; // one shot | ||
return SocketProviderError(sync::websocket::WebSocketError::websocket_unauthorized, | ||
"403 not authorized"); | ||
} | ||
return SocketProviderError(sync::websocket::WebSocketError::websocket_connection_failed, | ||
"Operation timed out"); | ||
}; | ||
|
||
the_app = app::App::get_app(app::App::CacheMode::Disabled, app_config); | ||
SyncTestFile config(the_app->current_user(), "realm"); | ||
config.sync_config->cancel_waits_on_nonfatal_error = true; | ||
config.sync_config->error_handler = err_handler; | ||
|
||
// User should be logged in at this point | ||
REQUIRE(config.sync_config->user->is_logged_in()); | ||
|
||
auto task = Realm::get_synchronized_realm(config); | ||
auto pf = util::make_promise_future<std::exception_ptr>(); | ||
task->start([&pf](auto ref, auto error) mutable { | ||
REQUIRE(!ref); | ||
REQUIRE(error); | ||
pf.promise.emplace_value(error); | ||
}); | ||
|
||
auto result = pf.future.get_no_throw(); | ||
REQUIRE(result.is_ok()); | ||
REQUIRE(result.get_value()); | ||
std::lock_guard<std::mutex> lock(mutex); | ||
REQUIRE(location_refresh_called); | ||
if (failure != FailureMode::location_fails) { | ||
REQUIRE(token_refresh_called); | ||
} | ||
} | ||
|
||
#endif // REALM_APP_SERVICES | ||
|
||
SECTION("read-only mode sets the schema version") { | ||
{ | ||
SharedRealm realm = Realm::get_shared_realm(config); | ||
|
@@ -1348,6 +1495,111 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { | |
} | ||
} | ||
|
||
#if REALM_ENABLE_AUTH_TESTS | ||
#if REALM_APP_SERVICES | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. REALM_ENABLE_AUTH_TESTS implies REALM_APP_SERVICES so they don't both need to be checked. |
||
|
||
TEST_CASE("Syhcnronized realm: AutoOpen", "[sync][baas][pbs][async open]") { | ||
const auto partition = random_string(100); | ||
auto schema = get_default_schema(); | ||
enum TestMode { expired_at_start, expired_by_websocket, websocket_fails }; | ||
enum FailureMode { location_fails, token_fails, token_not_authorized }; | ||
|
||
auto logger = util::Logger::get_default_logger(); | ||
auto transport = std::make_shared<HookedTransport<>>(); | ||
auto socket_provider = std::make_shared<HookedSocketProvider>(logger, "some user agent"); | ||
std::mutex mutex; | ||
|
||
// Create the app session and get the logged in user identity | ||
auto server_app_config = minimal_app_config("autoopen-realm", schema); | ||
TestAppSession session(create_app(server_app_config), transport, DeleteApp{true}, realm::ReconnectMode::normal, | ||
socket_provider); | ||
auto user = session.app()->current_user(); | ||
std::string identity = user->user_id(); | ||
REQUIRE(user->is_logged_in()); | ||
REQUIRE(!identity.empty()); | ||
// Reopen the App instance and retrieve the cached user | ||
session.reopen(false); | ||
user = session.app()->get_existing_logged_in_user(identity); | ||
|
||
SyncTestFile config(user, partition, schema); | ||
config.sync_config->cancel_waits_on_nonfatal_error = true; | ||
config.sync_config->error_handler = [&logger](std::shared_ptr<SyncSession> session, SyncError error) { | ||
logger->debug("The sync error handler caught an error: '%1' for '%2'", error.status, session->path()); | ||
// Ignore connection failed non-fatal errors and check for access token refresh unauthorized fatal errors | ||
if (error.status.code() == ErrorCodes::SyncConnectFailed) { | ||
REQUIRE_FALSE(error.is_fatal); | ||
return; | ||
} | ||
// If it's not SyncConnectFailed, then it should be AuthError | ||
REQUIRE(error.status.code() == ErrorCodes::AuthError); | ||
REQUIRE(error.is_fatal); | ||
}; | ||
|
||
bool not_authorized = false; | ||
bool token_refresh_called = false; | ||
bool location_refresh_called = false; | ||
|
||
FailureMode failure = FailureMode::location_fails; | ||
|
||
transport->request_hook = [&](const app::Request& req) -> std::optional<app::Response> { | ||
static constexpr int CURLE_OPERATION_TIMEDOUT = 28; | ||
std::lock_guard<std::mutex> lock(mutex); | ||
if (req.url.find("/auth/session") != std::string::npos) { | ||
token_refresh_called = true; | ||
if (failure == FailureMode::token_not_authorized) { | ||
return app::Response{403, 0, {}, "403 not authorized"}; | ||
} | ||
if (failure == FailureMode::token_fails) { | ||
return app::Response{0, CURLE_OPERATION_TIMEDOUT, {}, "Operation timed out"}; | ||
} | ||
} | ||
else if (req.url.find("/location") != std::string::npos) { | ||
location_refresh_called = true; | ||
if (failure == FailureMode::location_fails) { | ||
// Fake "offline/request timed out" custom error response | ||
return app::Response{0, CURLE_OPERATION_TIMEDOUT, {}, "Operation timed out"}; | ||
} | ||
} | ||
return std::nullopt; | ||
}; | ||
|
||
socket_provider->websocket_connect_func = [&]() -> std::optional<SocketProviderError> { | ||
if (not_authorized) { | ||
not_authorized = false; // one shot | ||
return SocketProviderError(sync::websocket::WebSocketError::websocket_unauthorized, "403 not authorized"); | ||
} | ||
return SocketProviderError(sync::websocket::WebSocketError::websocket_connection_failed, | ||
"Operation timed out"); | ||
}; | ||
|
||
auto task = Realm::get_synchronized_realm(config); | ||
auto pf = util::make_promise_future<std::exception_ptr>(); | ||
task->start([&pf](auto ref, auto error) mutable { | ||
REQUIRE(!ref); | ||
REQUIRE(error); | ||
pf.promise.emplace_value(error); | ||
}); | ||
|
||
auto result = pf.future.get_no_throw(); | ||
REQUIRE(result.is_ok()); | ||
REQUIRE(result.get_value()); | ||
{ | ||
std::lock_guard<std::mutex> lock(mutex); | ||
REQUIRE(location_refresh_called); | ||
if (failure != FailureMode::location_fails) { | ||
REQUIRE(token_refresh_called); | ||
} | ||
} | ||
|
||
transport->request_hook = nullptr; | ||
socket_provider->websocket_connect_func = nullptr; | ||
auto r = Realm::get_shared_realm(config); | ||
wait_for_download(*r); | ||
} | ||
|
||
#endif // REALM_APP_SERVICES | ||
#endif // REALM_ENABLE_AUTH_TESTS | ||
|
||
TEST_CASE("SharedRealm: convert", "[sync][pbs][convert]") { | ||
TestSyncManager tsm; | ||
ObjectSchema object_schema = {"object", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't appear to be used anywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was previously using this to get the exception thrown in the Async open start callback, but not anymore. Removed this unused code.