Skip to content

Commit 37177fb

Browse files
committed
Don't perform a large number of writes when opening a sync metadata Realm
Initializing sync users read from the metadata Realm used the same code path as initializing newly logged in users, resulting in it performing three no-op write transactions per user to write the data which was just read back to the Realm.
1 parent 7954334 commit 37177fb

File tree

9 files changed

+93
-147
lines changed

9 files changed

+93
-147
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- Deleting the server-side user via one of the SyncUsers left the other SyncUsers in an invalid state.
1313
- A SyncUser which was originally created via anonymous login and then linked to an identity would still be treated as an anonymous users and removed entirely on logout.
1414
(since v10.0.0)
15+
* Reading existing logged-in users on app startup from the sync metadata Realm performed three no-op writes per user on the metadata Realm.
1516

1617
### Breaking changes
1718
* SyncUser::provider_type() and realm_user_get_auth_provider() have been removed. Users don't have provider types; identities do.

src/realm/object-store/sync/app.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -595,9 +595,8 @@ void App::get_profile(const std::shared_ptr<SyncUser>& sync_user,
595595
SyncUserIdentity(get<std::string>(doc, "id"), get<std::string>(doc, "provider_type")));
596596
}
597597

598-
sync_user->update_identities(identities);
599-
sync_user->update_user_profile(SyncUserProfile(get<BsonDocument>(profile_json, "data")));
600-
sync_user->set_state(SyncUser::State::LoggedIn);
598+
sync_user->update_user_profile(std::move(identities),
599+
SyncUserProfile(get<BsonDocument>(profile_json, "data")));
601600
self->m_sync_manager->set_current_user(sync_user->identity());
602601
self->emit_change_to_subscribers(*self);
603602
}

src/realm/object-store/sync/sync_manager.cpp

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,7 @@ SyncManager::SyncManager() = default;
4646
void SyncManager::configure(std::shared_ptr<app::App> app, const std::string& sync_route,
4747
const SyncClientConfig& config)
4848
{
49-
struct UserCreationData {
50-
std::string identity;
51-
std::string refresh_token;
52-
std::string access_token;
53-
std::vector<SyncUserIdentity> identities;
54-
SyncUser::State state;
55-
std::string device_id;
56-
};
57-
58-
std::vector<UserCreationData> users_to_add;
49+
std::vector<std::shared_ptr<SyncUser>> users_to_add;
5950
{
6051
// Locking the mutex here ensures that it is released before locking m_user_mutex
6152
util::CheckedLockGuard lock(m_mutex);
@@ -113,11 +104,8 @@ void SyncManager::configure(std::shared_ptr<app::App> app, const std::string& sy
113104
auto user_data = users.get(i);
114105
auto refresh_token = user_data.refresh_token();
115106
auto access_token = user_data.access_token();
116-
auto device_id = user_data.device_id();
117107
if (!refresh_token.empty() && !access_token.empty()) {
118-
users_to_add.push_back(UserCreationData{user_data.identity(), std::move(refresh_token),
119-
std::move(access_token), user_data.identities(),
120-
user_data.state(), device_id});
108+
users_to_add.push_back(std::make_shared<SyncUser>(user_data, this));
121109
}
122110
}
123111

@@ -144,13 +132,7 @@ void SyncManager::configure(std::shared_ptr<app::App> app, const std::string& sy
144132
}
145133
{
146134
util::CheckedLockGuard lock(m_user_mutex);
147-
for (auto& user_data : users_to_add) {
148-
auto& identity = user_data.identity;
149-
auto user = std::make_shared<SyncUser>(user_data.refresh_token, identity, user_data.access_token,
150-
user_data.state, user_data.device_id, this);
151-
user->update_identities(user_data.identities);
152-
m_users.emplace_back(std::move(user));
153-
}
135+
m_users.insert(m_users.end(), users_to_add.begin(), users_to_add.end());
154136
}
155137
}
156138

@@ -351,17 +333,16 @@ bool SyncManager::perform_metadata_update(util::FunctionRef<void(SyncMetadataMan
351333
return true;
352334
}
353335

354-
std::shared_ptr<SyncUser> SyncManager::get_user(const std::string& user_id, std::string refresh_token,
355-
std::string access_token, std::string device_id)
336+
std::shared_ptr<SyncUser> SyncManager::get_user(const std::string& user_id, const std::string& refresh_token,
337+
const std::string& access_token, const std::string& device_id)
356338
{
357339
util::CheckedLockGuard lock(m_user_mutex);
358340
auto it = std::find_if(m_users.begin(), m_users.end(), [&](const auto& user) {
359341
return user->identity() == user_id && user->state() != SyncUser::State::Removed;
360342
});
361343
if (it == m_users.end()) {
362344
// No existing user.
363-
auto new_user = std::make_shared<SyncUser>(std::move(refresh_token), user_id, std::move(access_token),
364-
SyncUser::State::LoggedIn, device_id, this);
345+
auto new_user = std::make_shared<SyncUser>(refresh_token, user_id, access_token, device_id, this);
365346
m_users.emplace(m_users.begin(), new_user);
366347
{
367348
util::CheckedLockGuard lock(m_file_system_mutex);

src/realm/object-store/sync/sync_manager.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,9 @@ class SyncManager : public std::enable_shared_from_this<SyncManager> {
180180

181181
// Get a sync user for a given identity, or create one if none exists yet, and set its token.
182182
// If a logged-out user exists, it will marked as logged back in.
183-
std::shared_ptr<SyncUser> get_user(const std::string& id, std::string refresh_token, std::string access_token,
184-
std::string device_id) REQUIRES(!m_user_mutex, !m_file_system_mutex);
183+
std::shared_ptr<SyncUser> get_user(const std::string& user_id, const std::string& refresh_token,
184+
const std::string& access_token, const std::string& device_id)
185+
REQUIRES(!m_user_mutex, !m_file_system_mutex);
185186

186187
// Get an existing user for a given identifier, if one exists and is logged in.
187188
std::shared_ptr<SyncUser> get_existing_logged_in_user(const std::string& user_id) const REQUIRES(!m_user_mutex);

src/realm/object-store/sync/sync_user.cpp

Lines changed: 35 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -84,31 +84,50 @@ SyncUserIdentity::SyncUserIdentity(const std::string& id, const std::string& pro
8484
SyncUserContextFactory SyncUser::s_binding_context_factory;
8585
std::mutex SyncUser::s_binding_context_factory_mutex;
8686

87-
SyncUser::SyncUser(std::string refresh_token, std::string identity, std::string access_token, SyncUser::State state,
88-
std::string device_id, SyncManager* sync_manager)
89-
: m_state(state)
90-
, m_identity(std::move(identity))
91-
, m_refresh_token(RealmJWT(std::move(refresh_token)))
92-
, m_access_token(RealmJWT(std::move(access_token)))
93-
, m_device_id(std::move(device_id))
87+
SyncUser::SyncUser(const std::string& refresh_token, const std::string& id, const std::string& access_token,
88+
const std::string& device_id, SyncManager* sync_manager)
89+
: m_state(State::LoggedIn)
90+
, m_identity(id)
91+
, m_refresh_token(RealmJWT(refresh_token))
92+
, m_access_token(RealmJWT(access_token))
93+
, m_device_id(device_id)
9494
, m_sync_manager(sync_manager)
9595
{
9696
{
97-
std::lock_guard<std::mutex> lock(s_binding_context_factory_mutex);
97+
std::lock_guard lock(s_binding_context_factory_mutex);
9898
if (s_binding_context_factory) {
9999
m_binding_context = s_binding_context_factory();
100100
}
101101
}
102102

103103
m_sync_manager->perform_metadata_update([&](const auto& manager) NO_THREAD_SAFETY_ANALYSIS {
104104
auto metadata = manager.get_or_make_user_metadata(m_identity);
105-
metadata->set_state_and_tokens(state, m_access_token.token, m_refresh_token.token);
105+
metadata->set_state_and_tokens(State::LoggedIn, m_access_token.token, m_refresh_token.token);
106106
metadata->set_device_id(m_device_id);
107107
m_legacy_identities = metadata->legacy_identities();
108108
this->m_user_profile = metadata->profile();
109109
});
110110
}
111111

112+
SyncUser::SyncUser(const SyncUserMetadata& data, SyncManager* sync_manager)
113+
: m_state(data.state())
114+
, m_legacy_identities(data.legacy_identities())
115+
, m_identity(data.identity())
116+
, m_refresh_token(RealmJWT(data.refresh_token()))
117+
, m_access_token(RealmJWT(data.access_token()))
118+
, m_user_identities(data.identities())
119+
, m_user_profile(data.profile())
120+
, m_device_id(data.device_id())
121+
, m_sync_manager(sync_manager)
122+
{
123+
{
124+
std::lock_guard lock(s_binding_context_factory_mutex);
125+
if (s_binding_context_factory) {
126+
m_binding_context = s_binding_context_factory();
127+
}
128+
}
129+
}
130+
112131
std::shared_ptr<SyncManager> SyncUser::sync_manager() const
113132
{
114133
util::CheckedLockGuard lk(m_mutex);
@@ -218,41 +237,6 @@ std::vector<std::shared_ptr<SyncSession>> SyncUser::revive_sessions()
218237
return sessions_to_revive;
219238
}
220239

221-
void SyncUser::update_refresh_token(std::string&& token)
222-
{
223-
std::vector<std::shared_ptr<SyncSession>> sessions_to_revive;
224-
{
225-
util::CheckedLockGuard lock(m_mutex);
226-
util::CheckedLockGuard lock2(m_tokens_mutex);
227-
switch (m_state) {
228-
case State::Removed:
229-
return;
230-
case State::LoggedIn:
231-
m_refresh_token = RealmJWT(std::move(token));
232-
break;
233-
case State::LoggedOut: {
234-
m_refresh_token = RealmJWT(std::move(token));
235-
m_state = State::LoggedIn;
236-
sessions_to_revive = revive_sessions();
237-
break;
238-
}
239-
}
240-
241-
m_sync_manager->perform_metadata_update([&, raw_refresh_token = m_refresh_token.token](const auto& manager) {
242-
auto metadata = manager.get_or_make_user_metadata(m_identity);
243-
metadata->set_refresh_token(raw_refresh_token);
244-
});
245-
}
246-
// (Re)activate all pending sessions.
247-
// Note that we do this after releasing the lock, since the session may
248-
// need to access protected User state in the process of binding itself.
249-
for (auto& session : sessions_to_revive) {
250-
session->revive_if_needed();
251-
}
252-
253-
emit_change_to_subscribers(*this);
254-
}
255-
256240
void SyncUser::update_access_token(std::string&& token)
257241
{
258242
std::vector<std::shared_ptr<SyncSession>> sessions_to_revive;
@@ -295,22 +279,6 @@ std::vector<SyncUserIdentity> SyncUser::identities() const
295279
return m_user_identities;
296280
}
297281

298-
299-
void SyncUser::update_identities(std::vector<SyncUserIdentity> identities)
300-
{
301-
util::CheckedLockGuard lock(m_mutex);
302-
if (m_state != SyncUser::State::LoggedIn) {
303-
return;
304-
}
305-
306-
m_user_identities = identities;
307-
308-
m_sync_manager->perform_metadata_update([&](const auto& manager) {
309-
auto metadata = manager.get_or_make_user_metadata(m_identity);
310-
metadata->set_identities(identities);
311-
});
312-
}
313-
314282
void SyncUser::log_out()
315283
{
316284
// We'll extend the lifetime of SyncManager while holding m_mutex so that we know it's safe to call methods on it
@@ -446,18 +414,20 @@ util::Optional<bson::BsonDocument> SyncUser::custom_data() const
446414
return m_access_token.user_data;
447415
}
448416

449-
void SyncUser::update_user_profile(const SyncUserProfile& profile)
417+
void SyncUser::update_user_profile(std::vector<SyncUserIdentity> identities, SyncUserProfile profile)
450418
{
451419
util::CheckedLockGuard lock(m_mutex);
452-
if (m_state != SyncUser::State::LoggedIn) {
420+
if (m_state == SyncUser::State::Removed) {
453421
return;
454422
}
455423

456-
m_user_profile = profile;
424+
m_user_identities = std::move(identities);
425+
m_user_profile = std::move(profile);
457426

458-
m_sync_manager->perform_metadata_update([&](const auto& manager) {
427+
m_sync_manager->perform_metadata_update([&](const auto& manager) NO_THREAD_SAFETY_ANALYSIS {
459428
auto metadata = manager.get_or_make_user_metadata(m_identity);
460-
metadata->set_user_profile(profile);
429+
metadata->set_identities(m_user_identities);
430+
metadata->set_user_profile(m_user_profile);
461431
});
462432
}
463433

src/realm/object-store/sync/sync_user.hpp

Lines changed: 41 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ namespace app {
3838
struct AppError;
3939
class MongoClient;
4040
} // namespace app
41-
class SyncSession;
4241
class SyncManager;
42+
class SyncSession;
43+
class SyncUserMetadata;
4344

4445
// A superclass that bindings can inherit from in order to store information
4546
// upon a `SyncUser` object.
@@ -179,10 +180,6 @@ class SyncUser : public std::enable_shared_from_this<SyncUser>, public Subscriba
179180
Removed,
180181
};
181182

182-
// Don't use this directly; use the `SyncManager` APIs. Public for use with `make_shared`.
183-
SyncUser(std::string refresh_token, std::string id, std::string access_token, SyncUser::State state,
184-
std::string device_id, SyncManager* sync_manager);
185-
186183
// Return a list of all sessions belonging to this user.
187184
std::vector<std::shared_ptr<SyncSession>> all_sessions() REQUIRES(!m_mutex);
188185

@@ -192,28 +189,6 @@ class SyncUser : public std::enable_shared_from_this<SyncUser>, public Subscriba
192189
// for testing purposes, and for bindings for consumers that are servers or tools.
193190
std::shared_ptr<SyncSession> session_for_on_disk_path(const std::string& path) REQUIRES(!m_mutex);
194191

195-
// Update the user's state and refresh/access tokens atomically in a Realm transaction.
196-
// If the user is transitioning between LoggedIn and LoggedOut, then the access_token and
197-
// refresh token must be empty, and likewise must not be empty if transitioning between
198-
// logged out and logged in.
199-
// Note that this is called by the SyncManager, and should not be directly called.
200-
void update_state_and_tokens(SyncUser::State state, const std::string& access_token,
201-
const std::string& refresh_token) REQUIRES(!m_mutex, !m_tokens_mutex);
202-
203-
// Update the user's refresh token. If the user is logged out, it will log itself back in.
204-
// Note that this is called by the SyncManager, and should not be directly called.
205-
void update_refresh_token(std::string&& token) REQUIRES(!m_mutex, !m_tokens_mutex);
206-
207-
// Update the user's access token. If the user is logged out, it will log itself back in.
208-
// Note that this is called by the SyncManager, and should not be directly called.
209-
void update_access_token(std::string&& token) REQUIRES(!m_mutex, !m_tokens_mutex);
210-
211-
// Update the user's profile.
212-
void update_user_profile(const SyncUserProfile& profile) REQUIRES(!m_mutex);
213-
214-
// Update the user's identities.
215-
void update_identities(std::vector<SyncUserIdentity> identities) REQUIRES(!m_mutex);
216-
217192
// Log the user out and mark it as such. This will also close its associated Sessions.
218193
void log_out() REQUIRES(!m_mutex, !m_tokens_mutex);
219194

@@ -234,36 +209,65 @@ class SyncUser : public std::enable_shared_from_this<SyncUser>, public Subscriba
234209
}
235210

236211
std::string access_token() const REQUIRES(!m_tokens_mutex);
237-
238212
std::string refresh_token() const REQUIRES(!m_tokens_mutex);
239-
240213
std::string device_id() const REQUIRES(!m_mutex);
241-
242214
bool has_device_id() const REQUIRES(!m_mutex);
243-
244215
SyncUserProfile user_profile() const REQUIRES(!m_mutex);
245-
246216
std::vector<SyncUserIdentity> identities() const REQUIRES(!m_mutex);
217+
State state() const REQUIRES(!m_mutex);
247218

248219
// Custom user data embedded in the access token.
249220
util::Optional<bson::BsonDocument> custom_data() const REQUIRES(!m_tokens_mutex);
250221

251-
State state() const REQUIRES(!m_mutex);
252-
void set_state(SyncUser::State state) REQUIRES(!m_mutex);
253-
254222
std::shared_ptr<SyncUserContext> binding_context() const
255223
{
256224
return m_binding_context.load();
257225
}
258226

227+
// Optionally set a context factory. If so, must be set before any sessions are created.
228+
static void set_binding_context_factory(SyncUserContextFactory factory);
229+
230+
std::shared_ptr<SyncManager> sync_manager() const REQUIRES(!m_mutex);
231+
232+
/// Retrieves a general-purpose service client for the Realm Cloud service
233+
/// @param service_name The name of the cluster
234+
app::MongoClient mongo_client(const std::string& service_name) REQUIRES(!m_mutex);
235+
236+
// ------------------------------------------------------------------------
237+
// All of the following are called by `SyncManager` and are public only for
238+
// testing purposes. SDKs should not call these directly in non-test code
239+
// or expose them in the public API.
240+
241+
// Don't use this directly; use the `SyncManager` APIs. Public for use with `make_shared`.
242+
SyncUser(const std::string& refresh_token, const std::string& id, const std::string& access_token,
243+
const std::string& device_id, SyncManager* sync_manager);
244+
SyncUser(const SyncUserMetadata& data, SyncManager* sync_manager);
245+
246+
void set_state(SyncUser::State state) REQUIRES(!m_mutex);
247+
248+
// Update the user's state and refresh/access tokens atomically in a Realm transaction.
249+
// If the user is transitioning between LoggedIn and LoggedOut, then the access_token and
250+
// refresh token must be empty, and likewise must not be empty if transitioning between
251+
// logged out and logged in.
252+
// Note that this is called by the SyncManager, and should not be directly called.
253+
void update_state_and_tokens(SyncUser::State state, const std::string& access_token,
254+
const std::string& refresh_token) REQUIRES(!m_mutex, !m_tokens_mutex);
255+
256+
// Update the user's access token. If the user is logged out, it will log itself back in.
257+
// Note that this is called by the SyncManager, and should not be directly called.
258+
void update_access_token(std::string&& token) REQUIRES(!m_mutex, !m_tokens_mutex);
259+
260+
// Update the user's profile and identities.
261+
void update_user_profile(std::vector<SyncUserIdentity> identities, SyncUserProfile profile) REQUIRES(!m_mutex);
262+
259263
// Register a session to this user.
260264
// A registered session will be bound at the earliest opportunity: either
261265
// immediately, or upon the user becoming Active.
262266
// Note that this is called by the SyncManager, and should not be directly called.
263267
void register_session(std::shared_ptr<SyncSession>) REQUIRES(!m_mutex);
264268

265269
/// Refreshes the custom data for this user
266-
/// If update_location is true, the location metadata will be queried before the request
270+
/// If `update_location` is true, the location metadata will be queried before the request
267271
void refresh_custom_data(bool update_location,
268272
util::UniqueFunction<void(util::Optional<app::AppError>)> completion_block)
269273
REQUIRES(!m_mutex);
@@ -274,15 +278,7 @@ class SyncUser : public std::enable_shared_from_this<SyncUser>, public Subscriba
274278
/// true.
275279
bool access_token_refresh_required() const REQUIRES(!m_mutex, !m_tokens_mutex);
276280

277-
// Optionally set a context factory. If so, must be set before any sessions are created.
278-
static void set_binding_context_factory(SyncUserContextFactory factory);
279-
280-
std::shared_ptr<SyncManager> sync_manager() const REQUIRES(!m_mutex);
281-
282-
/// Retrieves a general-purpose service client for the Realm Cloud service
283-
/// @param service_name The name of the cluster
284-
app::MongoClient mongo_client(const std::string& service_name) REQUIRES(!m_mutex);
285-
281+
// Hook for testing access token timeouts
286282
void set_seconds_to_adjust_time_for_testing(int seconds)
287283
{
288284
m_seconds_to_adjust_time_for_testing.store(seconds);

0 commit comments

Comments
 (0)