• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

realm / realm-core / james.stone_381

25 Sep 2023 06:35PM UTC coverage: 90.919% (+0.03%) from 90.892%
james.stone_381

Pull #6670

Evergreen

ironage
optimize: only compare strings once
Pull Request #6670: Sorting stage 3

97114 of 177952 branches covered (0.0%)

879 of 887 new or added lines in 12 files covered. (99.1%)

105 existing lines in 17 files now uncovered.

236103 of 259684 relevant lines covered (90.92%)

7062315.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.44
/src/realm/object-store/sync/sync_manager.cpp
1
////////////////////////////////////////////////////////////////////////////
2
//
3
// Copyright 2016 Realm Inc.
4
//
5
// Licensed under the Apache License, Version 2.0 (the "License");
6
// you may not use this file except in compliance with the License.
7
// You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing, software
12
// distributed under the License is distributed on an "AS IS" BASIS,
13
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
// See the License for the specific language governing permissions and
15
// limitations under the License.
16
//
17
////////////////////////////////////////////////////////////////////////////
18

19
#include <realm/object-store/sync/sync_manager.hpp>
20

21
#include <realm/object-store/sync/impl/sync_client.hpp>
22
#include <realm/object-store/sync/impl/sync_file.hpp>
23
#include <realm/object-store/sync/impl/sync_metadata.hpp>
24
#include <realm/object-store/sync/sync_session.hpp>
25
#include <realm/object-store/sync/sync_user.hpp>
26
#include <realm/object-store/sync/app.hpp>
27
#include <realm/object-store/util/uuid.hpp>
28

29
#include <realm/util/sha_crypto.hpp>
30
#include <realm/util/hex_dump.hpp>
31

32
using namespace realm;
33
using namespace realm::_impl;
34

35
SyncClientTimeouts::SyncClientTimeouts()
36
    : connect_timeout(sync::Client::default_connect_timeout)
37
    , connection_linger_time(sync::Client::default_connection_linger_time)
38
    , ping_keepalive_period(sync::Client::default_ping_keepalive_period)
39
    , pong_keepalive_timeout(sync::Client::default_pong_keepalive_timeout)
40
    , fast_reconnect_limit(sync::Client::default_fast_reconnect_limit)
41
{
12,319✔
42
}
12,319✔
43

44
SyncManager::SyncManager() = default;
4,141✔
45

46
void SyncManager::configure(std::shared_ptr<app::App> app, const std::string& sync_route,
47
                            const SyncClientConfig& config)
48
{
4,139✔
49
    struct UserCreationData {
4,139✔
50
        std::string identity;
4,139✔
51
        std::string refresh_token;
4,139✔
52
        std::string access_token;
4,139✔
53
        std::string provider_type;
4,139✔
54
        std::vector<SyncUserIdentity> identities;
4,139✔
55
        SyncUser::State state;
4,139✔
56
        std::string device_id;
4,139✔
57
    };
4,139✔
58

2,034✔
59
    std::vector<UserCreationData> users_to_add;
4,139✔
60
    {
4,139✔
61
        // Locking the mutex here ensures that it is released before locking m_user_mutex
2,034✔
62
        util::CheckedLockGuard lock(m_mutex);
4,139✔
63
        m_app = app;
4,139✔
64
        m_sync_route = sync_route;
4,139✔
65
        m_config = std::move(config);
4,139✔
66
        if (m_sync_client)
4,139✔
67
            return;
×
68

2,034✔
69
        // create a new logger - if the logger_factory is updated later, a new
2,034✔
70
        // logger will be created at that time.
2,034✔
71
        do_make_logger();
4,139✔
72

2,034✔
73
        {
4,139✔
74
            util::CheckedLockGuard lock(m_file_system_mutex);
4,139✔
75

2,034✔
76
            // Set up the file manager.
2,034✔
77
            if (m_file_manager) {
4,139✔
78
                // Changing the base path for tests requires calling reset_for_testing()
79
                // first, and otherwise isn't supported
80
                REALM_ASSERT(m_file_manager->base_path() == m_config.base_file_path);
×
81
            }
×
82
            else {
4,139✔
83
                m_file_manager = std::make_unique<SyncFileManager>(m_config.base_file_path, app->config().app_id);
4,139✔
84
            }
4,139✔
85

2,034✔
86
            // Set up the metadata manager, and perform initial loading/purging work.
2,034✔
87
            if (m_metadata_manager || m_config.metadata_mode == MetadataMode::NoMetadata) {
4,139✔
88
                return;
54✔
89
            }
54✔
90

2,007✔
91
            bool encrypt = m_config.metadata_mode == MetadataMode::Encryption;
4,085✔
92
            m_metadata_manager = std::make_unique<SyncMetadataManager>(m_file_manager->metadata_path(), encrypt,
4,085✔
93
                                                                       m_config.custom_encryption_key);
4,085✔
94

2,007✔
95
            REALM_ASSERT(m_metadata_manager);
4,085✔
96

2,007✔
97
            // Perform our "on next startup" actions such as deleting Realm files
2,007✔
98
            // which we couldn't delete immediately due to them being in use
2,007✔
99
            std::vector<SyncFileActionMetadata> completed_actions;
4,085✔
100
            SyncFileActionMetadataResults file_actions = m_metadata_manager->all_pending_actions();
4,085✔
101
            for (size_t i = 0; i < file_actions.size(); i++) {
4,135✔
102
                auto file_action = file_actions.get(i);
50✔
103
                if (run_file_action(file_action)) {
50✔
104
                    completed_actions.emplace_back(std::move(file_action));
46✔
105
                }
46✔
106
            }
50✔
107
            for (auto& action : completed_actions) {
2,030✔
108
                action.remove();
46✔
109
            }
46✔
110

2,007✔
111
            // Load persisted users into the users map.
2,007✔
112
            SyncUserMetadataResults users = m_metadata_manager->all_unmarked_users();
4,085✔
113
            for (size_t i = 0; i < users.size(); i++) {
4,099✔
114
                auto user_data = users.get(i);
14✔
115
                auto refresh_token = user_data.refresh_token();
14✔
116
                auto access_token = user_data.access_token();
14✔
117
                auto device_id = user_data.device_id();
14✔
118
                if (!refresh_token.empty() && !access_token.empty()) {
14✔
119
                    users_to_add.push_back(UserCreationData{user_data.identity(), std::move(refresh_token),
12✔
120
                                                            std::move(access_token), user_data.provider_type(),
12✔
121
                                                            user_data.identities(), user_data.state(), device_id});
12✔
122
                }
12✔
123
            }
14✔
124

2,007✔
125
            // Delete any users marked for death.
2,007✔
126
            std::vector<SyncUserMetadata> dead_users;
4,085✔
127
            SyncUserMetadataResults users_to_remove = m_metadata_manager->all_users_marked_for_removal();
4,085✔
128
            dead_users.reserve(users_to_remove.size());
4,085✔
129
            for (size_t i = 0; i < users_to_remove.size(); i++) {
4,089✔
130
                auto user = users_to_remove.get(i);
4✔
131
                // FIXME: delete user data in a different way? (This deletes a logged-out user's data as soon as the
2✔
132
                // app launches again, which might not be how some apps want to treat their data.)
2✔
133
                try {
4✔
134
                    m_file_manager->remove_user_realms(user.identity(), user.realm_file_paths());
4✔
135
                    dead_users.emplace_back(std::move(user));
4✔
136
                }
4✔
137
                catch (FileAccessError const&) {
2✔
138
                    continue;
×
139
                }
×
140
            }
4✔
141
            for (auto& user : dead_users) {
4,085✔
142
                user.remove();
4✔
143
            }
4✔
144
        }
4,085✔
145
    }
4,085✔
146
    {
4,085✔
147
        util::CheckedLockGuard lock(m_user_mutex);
4,085✔
148
        for (auto& user_data : users_to_add) {
2,013✔
149
            auto& identity = user_data.identity;
12✔
150
            auto& provider_type = user_data.provider_type;
12✔
151
            auto user =
12✔
152
                std::make_shared<SyncUser>(user_data.refresh_token, identity, provider_type, user_data.access_token,
12✔
153
                                           user_data.state, user_data.device_id, this);
12✔
154
            user->update_identities(user_data.identities);
12✔
155
            m_users.emplace_back(std::move(user));
12✔
156
        }
12✔
157
    }
4,085✔
158
}
4,085✔
159

160
bool SyncManager::immediately_run_file_actions(const std::string& realm_path)
161
{
10✔
162
    util::CheckedLockGuard lock(m_file_system_mutex);
10✔
163
    if (!m_metadata_manager) {
10✔
164
        return false;
×
165
    }
×
166
    if (auto metadata = m_metadata_manager->get_file_action_metadata(realm_path)) {
10✔
167
        if (run_file_action(*metadata)) {
6✔
168
            metadata->remove();
6✔
169
            return true;
6✔
170
        }
6✔
171
    }
4✔
172
    return false;
4✔
173
}
4✔
174

175
// Perform a file action. Returns whether or not the file action can be removed.
176
bool SyncManager::run_file_action(SyncFileActionMetadata& md)
177
{
56✔
178
    switch (md.action()) {
56✔
179
        case SyncFileActionMetadata::Action::DeleteRealm:
14✔
180
            // Delete all the files for the given Realm.
7✔
181
            return m_file_manager->remove_realm(md.original_name());
14✔
182
        case SyncFileActionMetadata::Action::BackUpThenDeleteRealm:
42✔
183
            // Copy the primary Realm file to the recovery dir, and then delete the Realm.
21✔
184
            auto new_name = md.new_name();
42✔
185
            auto original_name = md.original_name();
42✔
186
            if (!util::File::exists(original_name)) {
42✔
187
                // The Realm file doesn't exist anymore.
9✔
188
                return true;
18✔
189
            }
18✔
190
            if (new_name && !util::File::exists(*new_name) &&
24✔
191
                m_file_manager->copy_realm_file(original_name, *new_name)) {
23✔
192
                // We successfully copied the Realm file to the recovery directory.
11✔
193
                bool did_remove = m_file_manager->remove_realm(original_name);
22✔
194
                // if the copy succeeded but not the delete, then running BackupThenDelete
11✔
195
                // a second time would fail, so change this action to just delete the original file.
11✔
196
                if (did_remove) {
22✔
197
                    return true;
20✔
198
                }
20✔
199
                md.set_action(SyncFileActionMetadata::Action::DeleteRealm);
2✔
200
                return false;
2✔
201
            }
2✔
202
            return false;
2✔
203
    }
×
204
    return false;
×
205
}
×
206

207
void SyncManager::reset_for_testing()
208
{
4,039✔
209
    {
4,039✔
210
        util::CheckedLockGuard lock(m_file_system_mutex);
4,039✔
211
        m_metadata_manager = nullptr;
4,039✔
212
    }
4,039✔
213

1,984✔
214
    {
4,039✔
215
        // Destroy all the users.
1,984✔
216
        util::CheckedLockGuard lock(m_user_mutex);
4,039✔
217
        for (auto& user : m_users) {
4,450✔
218
            user->detach_from_sync_manager();
4,450✔
219
        }
4,450✔
220
        m_users.clear();
4,039✔
221
        m_current_user = nullptr;
4,039✔
222
    }
4,039✔
223

1,984✔
224
    {
4,039✔
225
        util::CheckedLockGuard lock(m_mutex);
4,039✔
226
        // Stop the client. This will abort any uploads that inactive sessions are waiting for.
1,984✔
227
        if (m_sync_client)
4,039✔
228
            m_sync_client->stop();
4,039✔
229
    }
4,039✔
230

1,984✔
231
    {
4,039✔
232
        util::CheckedUniqueLock lock(m_session_mutex);
4,039✔
233

1,984✔
234
        bool no_sessions = !do_has_existing_sessions();
4,039✔
235
        // There's a race between this function and sessions tearing themselves down waiting for m_session_mutex.
1,984✔
236
        // So we give up to a 5 second grace period for any sessions being torn down to unregister themselves.
1,984✔
237
        auto since_poll_start = [start = std::chrono::steady_clock::now()] {
1,984✔
238
            return std::chrono::steady_clock::now() - start;
×
239
        };
×
240
        for (; !no_sessions && since_poll_start() < std::chrono::seconds(5);
4,039!
241
             no_sessions = !do_has_existing_sessions()) {
1,984✔
242
            lock.unlock();
×
243
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
×
244
            lock.lock();
×
245
        }
×
246
        // Callers of `SyncManager::reset_for_testing` should ensure there are no existing sessions
1,984✔
247
        // prior to calling `reset_for_testing`.
1,984✔
248
        if (!no_sessions) {
4,039✔
249
            util::CheckedLockGuard lock(m_mutex);
×
250
            for (auto session : m_sessions) {
×
251
                m_logger_ptr->error("open session at path '%1'", session.first);
×
252
            }
×
253
        }
×
254
        REALM_ASSERT_RELEASE(no_sessions);
4,039✔
255

1,984✔
256
        // Destroy any inactive sessions.
1,984✔
257
        // FIXME: We shouldn't have any inactive sessions at this point! Sessions are expected to
1,984✔
258
        // remain inactive until their final upload completes, at which point they are unregistered
1,984✔
259
        // and destroyed. Our call to `sync::Client::stop` above aborts all uploads, so all sessions
1,984✔
260
        // should have already been destroyed.
1,984✔
261
        m_sessions.clear();
4,039✔
262
    }
4,039✔
263

1,984✔
264
    {
4,039✔
265
        util::CheckedLockGuard lock(m_mutex);
4,039✔
266
        // Destroy the client now that we have no remaining sessions.
1,984✔
267
        m_sync_client = nullptr;
4,039✔
268

1,984✔
269
        // Reset even more state.
1,984✔
270
        m_config = {};
4,039✔
271
        m_logger_ptr.reset();
4,039✔
272
        m_sync_route = "";
4,039✔
273
    }
4,039✔
274

1,984✔
275
    {
4,039✔
276
        util::CheckedLockGuard lock(m_file_system_mutex);
4,039✔
277
        if (m_file_manager)
4,039✔
278
            util::try_remove_dir_recursive(m_file_manager->base_path());
4,039✔
279
        m_file_manager = nullptr;
4,039✔
280
    }
4,039✔
281
}
4,039✔
282

283
void SyncManager::set_log_level(util::Logger::Level level) noexcept
284
{
×
285
    util::CheckedLockGuard lock(m_mutex);
×
286
    m_config.log_level = level;
×
287
    // Update the level threshold in the already created logger
288
    if (m_logger_ptr) {
×
289
        m_logger_ptr->set_level_threshold(level);
×
290
    }
×
291
}
×
292

293
void SyncManager::set_logger_factory(SyncClientConfig::LoggerFactory factory)
294
{
×
295
    util::CheckedLockGuard lock(m_mutex);
×
296
    m_config.logger_factory = std::move(factory);
×
297

298
    if (m_sync_client)
×
299
        throw std::logic_error("Cannot set the logger factory after creating the sync client");
×
300

301
    // Create a new logger using the new factory
302
    do_make_logger();
×
303
}
×
304

305
void SyncManager::do_make_logger()
306
{
4,139✔
307
    if (m_config.logger_factory) {
4,139✔
308
        m_logger_ptr = m_config.logger_factory(m_config.log_level);
×
309
    }
×
310
    else {
4,139✔
311
        m_logger_ptr = util::Logger::get_default_logger();
4,139✔
312
    }
4,139✔
313
}
4,139✔
314

315
const std::shared_ptr<util::Logger>& SyncManager::get_logger() const
316
{
564✔
317
    util::CheckedLockGuard lock(m_mutex);
564✔
318
    return m_logger_ptr;
564✔
319
}
564✔
320

321
void SyncManager::set_user_agent(std::string user_agent)
322
{
×
323
    util::CheckedLockGuard lock(m_mutex);
×
324
    m_config.user_agent_application_info = std::move(user_agent);
×
325
}
×
326

327
void SyncManager::set_timeouts(SyncClientTimeouts timeouts)
328
{
×
329
    util::CheckedLockGuard lock(m_mutex);
×
330
    m_config.timeouts = timeouts;
×
331
}
×
332

333
void SyncManager::reconnect() const
334
{
2✔
335
    util::CheckedLockGuard lock(m_session_mutex);
2✔
336
    for (auto& it : m_sessions) {
1✔
337
        it.second->handle_reconnect();
×
338
    }
×
339
}
2✔
340

341
util::Logger::Level SyncManager::log_level() const noexcept
342
{
×
343
    util::CheckedLockGuard lock(m_mutex);
×
344
    return m_config.log_level;
×
345
}
×
346

347
bool SyncManager::perform_metadata_update(util::FunctionRef<void(SyncMetadataManager&)> update_function) const
348
{
11,629✔
349
    util::CheckedLockGuard lock(m_file_system_mutex);
11,629✔
350
    if (!m_metadata_manager) {
11,629✔
351
        return false;
83✔
352
    }
83✔
353
    update_function(*m_metadata_manager);
11,546✔
354
    return true;
11,546✔
355
}
11,546✔
356

357
std::shared_ptr<SyncUser> SyncManager::get_user(const std::string& user_id, std::string refresh_token,
358
                                                std::string access_token, const std::string provider_type,
359
                                                std::string device_id)
360
{
7,832✔
361
    util::CheckedLockGuard lock(m_user_mutex);
7,832✔
362
    auto it = std::find_if(m_users.begin(), m_users.end(), [user_id, provider_type](const auto& user) {
5,994✔
363
        return user->identity() == user_id && user->provider_type() == provider_type &&
4,227✔
364
               user->state() != SyncUser::State::Removed;
3,778✔
365
    });
4,227✔
366
    if (it == m_users.end()) {
7,832✔
367
        // No existing user.
2,213✔
368
        auto new_user =
4,502✔
369
            std::make_shared<SyncUser>(std::move(refresh_token), user_id, provider_type, std::move(access_token),
4,502✔
370
                                       SyncUser::State::LoggedIn, device_id, this);
4,502✔
371
        m_users.emplace(m_users.begin(), new_user);
4,502✔
372
        {
4,502✔
373
            util::CheckedLockGuard lock(m_file_system_mutex);
4,502✔
374
            // m_current_user is normally set very indirectly via the metadata manger
2,213✔
375
            if (!m_metadata_manager)
4,502✔
376
                m_current_user = new_user;
38✔
377
        }
4,502✔
378
        return new_user;
4,502✔
379
    }
4,502✔
380
    else { // LoggedOut => LoggedIn
3,330✔
381
        auto user = *it;
3,330✔
382
        REALM_ASSERT(user->state() != SyncUser::State::Removed);
3,330✔
383
        user->update_state_and_tokens(SyncUser::State::LoggedIn, std::move(access_token), std::move(refresh_token));
3,330✔
384
        return user;
3,330✔
385
    }
3,330✔
386
}
7,832✔
387

388
std::vector<std::shared_ptr<SyncUser>> SyncManager::all_users()
389
{
238✔
390
    util::CheckedLockGuard lock(m_user_mutex);
238✔
391
    m_users.erase(std::remove_if(m_users.begin(), m_users.end(),
238✔
392
                                 [](auto& user) {
230✔
393
                                     bool should_remove = (user->state() == SyncUser::State::Removed);
222✔
394
                                     if (should_remove) {
222✔
395
                                         user->detach_from_sync_manager();
22✔
396
                                     }
22✔
397
                                     return should_remove;
222✔
398
                                 }),
222✔
399
                  m_users.end());
238✔
400
    return m_users;
238✔
401
}
238✔
402

403
std::shared_ptr<SyncUser> SyncManager::get_user_for_identity(std::string const& identity) const noexcept
404
{
1,055✔
405
    auto is_active_user = [identity](auto& el) {
1,061✔
406
        return el->identity() == identity;
1,061✔
407
    };
1,061✔
408
    auto it = std::find_if(m_users.begin(), m_users.end(), is_active_user);
1,055✔
409
    return it == m_users.end() ? nullptr : *it;
1,042✔
410
}
1,055✔
411

412
std::shared_ptr<SyncUser> SyncManager::get_current_user() const
413
{
923✔
414
    util::CheckedLockGuard lock(m_user_mutex);
923✔
415

452✔
416
    if (m_current_user)
923✔
417
        return m_current_user;
863✔
418
    util::CheckedLockGuard fs_lock(m_file_system_mutex);
60✔
419
    if (!m_metadata_manager)
60✔
420
        return nullptr;
×
421

30✔
422
    auto cur_user_ident = m_metadata_manager->get_current_user_identity();
60✔
423
    return cur_user_ident ? get_user_for_identity(*cur_user_ident) : nullptr;
49✔
424
}
60✔
425

426
void SyncManager::log_out_user(const std::string& user_id)
427
{
93✔
428
    util::CheckedLockGuard lock(m_user_mutex);
93✔
429

46✔
430
    // Move this user to the end of the vector
46✔
431
    if (m_users.size() > 1) {
93✔
432
        auto it = std::find_if(m_users.begin(), m_users.end(), [user_id](const auto& user) {
30✔
433
            return user->identity() == user_id;
30✔
434
        });
30✔
435

11✔
436
        if (it != m_users.end())
23✔
437
            std::rotate(it, it + 1, m_users.end());
23✔
438
    }
23✔
439

46✔
440
    util::CheckedLockGuard fs_lock(m_file_system_mutex);
93✔
441
    bool was_active = (m_current_user && m_current_user->identity() == user_id) ||
93✔
442
                      (m_metadata_manager && m_metadata_manager->get_current_user_identity() == user_id);
62✔
443
    if (!was_active)
93✔
444
        return;
3✔
445

45✔
446
    // Set the current active user to the next logged in user, or null if none
45✔
447
    for (auto& user : m_users) {
98✔
448
        if (user->state() == SyncUser::State::LoggedIn) {
98✔
449
            if (m_metadata_manager)
14✔
450
                m_metadata_manager->set_current_user_identity(user->identity());
14✔
451
            m_current_user = user;
14✔
452
            return;
14✔
453
        }
14✔
454
    }
98✔
455

45✔
456
    if (m_metadata_manager)
83✔
457
        m_metadata_manager->set_current_user_identity("");
68✔
458
    m_current_user = nullptr;
76✔
459
}
76✔
460

461
void SyncManager::set_current_user(const std::string& user_id)
462
{
984✔
463
    util::CheckedLockGuard lock(m_user_mutex);
984✔
464

481✔
465
    m_current_user = get_user_for_identity(user_id);
984✔
466
    util::CheckedLockGuard fs_lock(m_file_system_mutex);
984✔
467
    if (m_metadata_manager)
984✔
468
        m_metadata_manager->set_current_user_identity(user_id);
982✔
469
}
984✔
470

471
void SyncManager::remove_user(const std::string& user_id)
472
{
27✔
473
    util::CheckedLockGuard lock(m_user_mutex);
27✔
474
    auto user = get_user_for_identity(user_id);
27✔
475
    if (!user)
27✔
476
        return;
×
477
    user->set_state(SyncUser::State::Removed);
27✔
478

13✔
479
    util::CheckedLockGuard fs_lock(m_file_system_mutex);
27✔
480
    if (!m_metadata_manager)
27✔
481
        return;
×
482

13✔
483
    for (size_t i = 0; i < m_metadata_manager->all_unmarked_users().size(); i++) {
63✔
484
        auto metadata = m_metadata_manager->all_unmarked_users().get(i);
36✔
485
        if (user->identity() == metadata.identity()) {
36✔
486
            metadata.mark_for_removal();
27✔
487
        }
27✔
488
    }
36✔
489
}
27✔
490

491
void SyncManager::delete_user(const std::string& user_id)
492
{
10✔
493
    util::CheckedLockGuard lock(m_user_mutex);
10✔
494
    // Avoid itterating over m_users twice by not calling `get_user_for_identity`.
5✔
495
    auto it = std::find_if(m_users.begin(), m_users.end(), [&user_id](auto& user) {
10✔
496
        return user->identity() == user_id;
10✔
497
    });
10✔
498
    auto user = it == m_users.end() ? nullptr : *it;
10✔
499

5✔
500
    if (!user)
10✔
501
        return;
×
502

5✔
503
    // Deletion should happen immediately, not when we do the cleanup
5✔
504
    // task on next launch.
5✔
505
    m_users.erase(it);
10✔
506
    user->detach_from_sync_manager();
10✔
507

5✔
508
    if (m_current_user && m_current_user->identity() == user->identity())
10✔
509
        m_current_user = nullptr;
10✔
510

5✔
511
    util::CheckedLockGuard fs_lock(m_file_system_mutex);
10✔
512
    if (!m_metadata_manager)
10✔
513
        return;
×
514

5✔
515
    auto users = m_metadata_manager->all_unmarked_users();
10✔
516
    for (size_t i = 0; i < users.size(); i++) {
12✔
517
        auto metadata = users.get(i);
12✔
518
        if (user->identity() == metadata.identity()) {
12✔
519
            m_file_manager->remove_user_realms(metadata.identity(), metadata.realm_file_paths());
10✔
520
            metadata.remove();
10✔
521
            break;
10✔
522
        }
10✔
523
    }
12✔
524
}
10✔
525

526
SyncManager::~SyncManager() NO_THREAD_SAFETY_ANALYSIS
527
{
4,138✔
528
    // Grab the current sessions under a lock so we can shut them down. We have to
2,032✔
529
    // release the lock before calling them as shutdown_and_wait() will call
2,032✔
530
    // back into us.
2,032✔
531
    decltype(m_sessions) current_sessions;
4,138✔
532
    {
4,138✔
533
        util::CheckedLockGuard lk(m_session_mutex);
4,138✔
534
        m_sessions.swap(current_sessions);
4,138✔
535
    }
4,138✔
536

2,032✔
537
    for (auto& [_, session] : current_sessions) {
2,032✔
UNCOV
538
        session->detach_from_sync_manager();
×
UNCOV
539
    }
×
540

2,032✔
541
    {
4,138✔
542
        util::CheckedLockGuard lk(m_user_mutex);
4,138✔
543
        for (auto& user : m_users) {
2,047✔
544
            user->detach_from_sync_manager();
30✔
545
        }
30✔
546
    }
4,138✔
547

2,032✔
548
    {
4,138✔
549
        util::CheckedLockGuard lk(m_mutex);
4,138✔
550
        // Stop the client. This will abort any uploads that inactive sessions are waiting for.
2,032✔
551
        if (m_sync_client)
4,138✔
552
            m_sync_client->stop();
40✔
553
    }
4,138✔
554
}
4,138✔
555

556
std::shared_ptr<SyncUser> SyncManager::get_existing_logged_in_user(const std::string& user_id) const
557
{
6✔
558
    util::CheckedLockGuard lock(m_user_mutex);
6✔
559
    auto user = get_user_for_identity(user_id);
6✔
560
    return user && user->state() == SyncUser::State::LoggedIn ? user : nullptr;
6✔
561
}
6✔
562

563
struct UnsupportedBsonPartition : public std::logic_error {
564
    UnsupportedBsonPartition(std::string msg)
565
        : std::logic_error(msg)
566
    {
×
567
    }
×
568
};
569

570
static std::string string_from_partition(const std::string& partition)
571
{
54✔
572
    bson::Bson partition_value = bson::parse(partition);
54✔
573
    switch (partition_value.type()) {
54✔
574
        case bson::Bson::Type::Int32:
2✔
575
            return util::format("i_%1", static_cast<int32_t>(partition_value));
2✔
576
        case bson::Bson::Type::Int64:
2✔
577
            return util::format("l_%1", static_cast<int64_t>(partition_value));
2✔
578
        case bson::Bson::Type::String:
44✔
579
            return util::format("s_%1", static_cast<std::string>(partition_value));
44✔
580
        case bson::Bson::Type::ObjectId:
2✔
581
            return util::format("o_%1", static_cast<ObjectId>(partition_value).to_string());
2✔
582
        case bson::Bson::Type::Uuid:
2✔
583
            return util::format("u_%1", static_cast<UUID>(partition_value).to_string());
2✔
584
        case bson::Bson::Type::Null:
2✔
585
            return "null";
2✔
586
        default:
✔
587
            throw UnsupportedBsonPartition(util::format("Unsupported partition key value: '%1'. Only int, string "
×
588
                                                        "UUID and ObjectId types are currently supported.",
×
589
                                                        partition_value.to_string()));
×
590
    }
54✔
591
}
54✔
592

593
std::string SyncManager::path_for_realm(const SyncConfig& config, util::Optional<std::string> custom_file_name) const
594
{
66✔
595
    auto user = config.user;
66✔
596
    REALM_ASSERT(user);
66✔
597
    std::string path;
66✔
598
    {
66✔
599
        util::CheckedLockGuard lock(m_file_system_mutex);
66✔
600
        REALM_ASSERT(m_file_manager);
66✔
601

33✔
602
        // Attempt to make a nicer filename which will ease debugging when
33✔
603
        // locating files in the filesystem.
33✔
604
        auto file_name = [&]() -> std::string {
66✔
605
            if (custom_file_name) {
66✔
606
                return *custom_file_name;
10✔
607
            }
10✔
608
            if (config.flx_sync_requested) {
56✔
609
                REALM_ASSERT_DEBUG(config.partition_value.empty());
2✔
610
                return "flx_sync_default";
2✔
611
            }
2✔
612
            return string_from_partition(config.partition_value);
54✔
613
        }();
54✔
614
        path = m_file_manager->realm_file_path(user->identity(), user->local_identity(), file_name,
66✔
615
                                               config.partition_value);
66✔
616
    }
66✔
617
    // Report the use of a Realm for this user, so the metadata can track it for clean up.
33✔
618
    perform_metadata_update([&](const auto& manager) {
56✔
619
        auto metadata = manager.get_or_make_user_metadata(user->identity(), user->provider_type());
46✔
620
        metadata->add_realm_file_path(path);
46✔
621
    });
46✔
622
    return path;
66✔
623
}
66✔
624

625
std::string SyncManager::recovery_directory_path(util::Optional<std::string> const& custom_dir_name) const
626
{
74✔
627
    util::CheckedLockGuard lock(m_file_system_mutex);
74✔
628
    REALM_ASSERT(m_file_manager);
74✔
629
    return m_file_manager->recovery_directory_path(custom_dir_name);
74✔
630
}
74✔
631

632
std::vector<std::shared_ptr<SyncSession>> SyncManager::get_all_sessions() const
633
{
52✔
634
    util::CheckedLockGuard lock(m_session_mutex);
52✔
635
    std::vector<std::shared_ptr<SyncSession>> sessions;
52✔
636
    for (auto& [_, session] : m_sessions) {
102✔
637
        if (auto external_reference = session->existing_external_reference())
102✔
638
            sessions.push_back(std::move(external_reference));
100✔
639
    }
102✔
640
    return sessions;
52✔
641
}
52✔
642

643
std::shared_ptr<SyncSession> SyncManager::get_existing_active_session(const std::string& path) const
644
{
×
645
    util::CheckedLockGuard lock(m_session_mutex);
×
646
    if (auto session = get_existing_session_locked(path)) {
×
647
        if (auto external_reference = session->existing_external_reference())
×
648
            return external_reference;
×
649
    }
×
650
    return nullptr;
×
651
}
×
652

653
std::shared_ptr<SyncSession> SyncManager::get_existing_session_locked(const std::string& path) const
654
{
2,695✔
655
    auto it = m_sessions.find(path);
2,695✔
656
    return it == m_sessions.end() ? nullptr : it->second;
2,587✔
657
}
2,695✔
658

659
std::shared_ptr<SyncSession> SyncManager::get_existing_session(const std::string& path) const
660
{
1,362✔
661
    util::CheckedLockGuard lock(m_session_mutex);
1,362✔
662
    if (auto session = get_existing_session_locked(path))
1,362✔
663
        return session->external_reference();
206✔
664

499✔
665
    return nullptr;
1,156✔
666
}
1,156✔
667

668
std::shared_ptr<SyncSession> SyncManager::get_session(std::shared_ptr<DB> db, const RealmConfig& config)
669
{
1,333✔
670
    auto& client = get_sync_client(); // Throws
1,333✔
671
#ifndef __EMSCRIPTEN__
1,333✔
672
    auto path = db->get_path();
1,333✔
673
    REALM_ASSERT_EX(path == config.path, path, config.path);
1,333✔
674
#else
675
    auto path = config.path;
676
#endif
677
    REALM_ASSERT(config.sync_config);
1,333✔
678

580✔
679
    util::CheckedUniqueLock lock(m_session_mutex);
1,333✔
680
    if (auto session = get_existing_session_locked(path)) {
1,333✔
681
        config.sync_config->user->register_session(session);
8✔
682
        return session->external_reference();
8✔
683
    }
8✔
684

576✔
685
    auto shared_session = SyncSession::create(client, std::move(db), config, this);
1,325✔
686
    m_sessions[path] = shared_session;
1,325✔
687

576✔
688
    // Create the external reference immediately to ensure that the session will become
576✔
689
    // inactive if an exception is thrown in the following code.
576✔
690
    auto external_reference = shared_session->external_reference();
1,325✔
691
    // unlocking m_session_mutex here prevents a deadlock for synchronous network
576✔
692
    // transports such as the unit test suite, in the case where the log in request is
576✔
693
    // denied by the server: Active -> WaitingForAccessToken -> handle_refresh(401
576✔
694
    // error) -> user.log_out() -> unregister_session (locks m_session_mutex again)
576✔
695
    lock.unlock();
1,325✔
696
    config.sync_config->user->register_session(std::move(shared_session));
1,325✔
697

576✔
698
    return external_reference;
1,325✔
699
}
1,325✔
700

701
bool SyncManager::has_existing_sessions()
702
{
12✔
703
    util::CheckedLockGuard lock(m_session_mutex);
12✔
704
    return do_has_existing_sessions();
12✔
705
}
12✔
706

707
bool SyncManager::do_has_existing_sessions()
708
{
4,051✔
709
    return std::any_of(m_sessions.begin(), m_sessions.end(), [](auto& element) {
2,007✔
710
        return element.second->existing_external_reference();
39✔
711
    });
39✔
712
}
4,051✔
713

714
void SyncManager::wait_for_sessions_to_terminate()
715
{
58✔
716
    auto& client = get_sync_client(); // Throws
58✔
717
    client.wait_for_session_terminations();
58✔
718
}
58✔
719

720
void SyncManager::unregister_session(const std::string& path)
721
{
2,051✔
722
    util::CheckedUniqueLock lock(m_session_mutex);
2,051✔
723
    auto it = m_sessions.find(path);
2,051✔
724
    if (it == m_sessions.end()) {
2,051✔
725
        // The session may already be unregistered. This always happens in the
1✔
726
        // SyncManager destructor, and can also happen due to multiple threads
1✔
727
        // tearing things down at once.
1✔
728
        return;
3✔
729
    }
3✔
730

868✔
731
    // Sync session teardown calls this function, so we need to be careful with
868✔
732
    // locking here. We need to unlock `m_session_mutex` before we do anything
868✔
733
    // which could result in a re-entrant call or we'll deadlock, which in this
868✔
734
    // function means unlocking before we destroy a `shared_ptr<SyncSession>`
868✔
735
    // (either the external reference or internal reference versions).
868✔
736
    // The external reference version will only be the final reference if
868✔
737
    // another thread drops a reference while we're in this function.
868✔
738
    // Dropping the final internal reference does not appear to ever actually
868✔
739
    // result in a recursive call to this function at the time this comment was
868✔
740
    // written, but releasing the lock in that case as well is still safer.
868✔
741

868✔
742
    if (auto existing_session = it->second->existing_external_reference()) {
2,048✔
743
        // We got here because the session entered the inactive state, but
312✔
744
        // there's still someone referencing it so we should leave it be. This
312✔
745
        // can happen if the user was logged out, or if all Realms using the
312✔
746
        // session were destroyed but the SDK user is holding onto the session.
312✔
747

312✔
748
        // Explicit unlock so that `existing_session`'s destructor runs after
312✔
749
        // the unlock for the reasons noted above
312✔
750
        lock.unlock();
760✔
751
        return;
760✔
752
    }
760✔
753

556✔
754
    // Remove the session from the map while holding the lock, but then defer
556✔
755
    // destroying it until after we unlock the mutex for the reasons noted above.
556✔
756
    auto session = m_sessions.extract(it);
1,288✔
757
    lock.unlock();
1,288✔
758
}
1,288✔
759

760
void SyncManager::set_session_multiplexing(bool allowed)
761
{
4✔
762
    util::CheckedLockGuard lock(m_mutex);
4✔
763
    if (m_config.multiplex_sessions == allowed)
4✔
764
        return; // Already enabled, we can ignore
2✔
765

1✔
766
    if (m_sync_client)
2✔
767
        throw std::logic_error("Cannot enable session multiplexing after creating the sync client");
×
768

1✔
769
    m_config.multiplex_sessions = allowed;
2✔
770
}
2✔
771

772
SyncClient& SyncManager::get_sync_client() const
773
{
5,472✔
774
    util::CheckedLockGuard lock(m_mutex);
5,472✔
775
    if (!m_sync_client)
5,472✔
776
        m_sync_client = create_sync_client(); // Throws
4,079✔
777
    return *m_sync_client;
5,472✔
778
}
5,472✔
779

780
std::unique_ptr<SyncClient> SyncManager::create_sync_client() const
781
{
4,079✔
782
    return std::make_unique<SyncClient>(m_logger_ptr, m_config, weak_from_this());
4,079✔
783
}
4,079✔
784

785
util::Optional<SyncAppMetadata> SyncManager::app_metadata() const
786
{
5,175✔
787
    util::CheckedLockGuard lock(m_file_system_mutex);
5,175✔
788
    if (!m_metadata_manager) {
5,175✔
789
        return util::none;
62✔
790
    }
62✔
791
    return m_metadata_manager->get_app_metadata();
5,113✔
792
}
5,113✔
793

794
void SyncManager::close_all_sessions()
795
{
×
796
    // log_out() will call unregister_session(), which requires m_session_mutex,
797
    // so we need to iterate over them without holding the lock.
798
    decltype(m_sessions) sessions;
×
799
    {
×
800
        util::CheckedLockGuard lk(m_session_mutex);
×
801
        m_sessions.swap(sessions);
×
802
    }
×
803

804
    for (auto& [_, session] : sessions) {
×
805
        session->force_close();
×
806
    }
×
807

808
    get_sync_client().wait_for_session_terminations();
×
809
}
×
810

811
void SyncManager::OnlyForTesting::voluntary_disconnect_all_connections(SyncManager& mgr)
812
{
6✔
813
    mgr.get_sync_client().voluntary_disconnect_all_connections();
6✔
814
}
6✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc