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

realm / realm-core / 1874

27 Nov 2023 09:40PM UTC coverage: 91.676% (-0.02%) from 91.695%
1874

push

Evergreen

web-flow
Fix a bunch of throw statements to use Realm exceptions (#7141)

* Fix a bunch of throw statements to use Realm exceptions
* check correct exception in test
* clang-format and replaced a couple of std::exceptions in SyncManager
* Updated changelog

---------

Co-authored-by: Jonathan Reams <jbreams@mongodb.com>
Co-authored-by: Jørgen Edelbo <jorgen.edelbo@mongodb.com>

92402 of 169340 branches covered (0.0%)

2 of 77 new or added lines in 7 files covered. (2.6%)

98 existing lines in 17 files now uncovered.

231851 of 252903 relevant lines covered (91.68%)

6068365.17 hits per line

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

86.94
/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
#include <realm/exceptions.hpp>
33

34
using namespace realm;
35
using namespace realm::_impl;
36

37
SyncClientTimeouts::SyncClientTimeouts()
38
    : connect_timeout(sync::Client::default_connect_timeout)
39
    , connection_linger_time(sync::Client::default_connection_linger_time)
40
    , ping_keepalive_period(sync::Client::default_ping_keepalive_period)
41
    , pong_keepalive_timeout(sync::Client::default_pong_keepalive_timeout)
42
    , fast_reconnect_limit(sync::Client::default_fast_reconnect_limit)
43
{
13,157✔
44
}
13,157✔
45

46
SyncManager::SyncManager() = default;
4,421✔
47

48
void SyncManager::configure(std::shared_ptr<app::App> app, const std::string& sync_route,
49
                            const SyncClientConfig& config)
50
{
4,419✔
51
    std::vector<std::shared_ptr<SyncUser>> users_to_add;
4,419✔
52
    {
4,419✔
53
        // Locking the mutex here ensures that it is released before locking m_user_mutex
2,174✔
54
        util::CheckedLockGuard lock(m_mutex);
4,419✔
55
        m_app = app;
4,419✔
56
        m_sync_route = sync_route;
4,419✔
57
        m_config = std::move(config);
4,419✔
58
        if (m_sync_client)
4,419✔
59
            return;
×
60

2,174✔
61
        // create a new logger - if the logger_factory is updated later, a new
2,174✔
62
        // logger will be created at that time.
2,174✔
63
        do_make_logger();
4,419✔
64

2,174✔
65
        {
4,419✔
66
            util::CheckedLockGuard lock(m_file_system_mutex);
4,419✔
67

2,174✔
68
            // Set up the file manager.
2,174✔
69
            if (m_file_manager) {
4,419✔
70
                // Changing the base path for tests requires calling reset_for_testing()
71
                // first, and otherwise isn't supported
72
                REALM_ASSERT(m_file_manager->base_path() == m_config.base_file_path);
×
73
            }
×
74
            else {
4,419✔
75
                m_file_manager = std::make_unique<SyncFileManager>(m_config.base_file_path, app->config().app_id);
4,419✔
76
            }
4,419✔
77

2,174✔
78
            // Set up the metadata manager, and perform initial loading/purging work.
2,174✔
79
            if (m_metadata_manager || m_config.metadata_mode == MetadataMode::NoMetadata) {
4,419✔
80
                return;
60✔
81
            }
60✔
82

2,144✔
83
            bool encrypt = m_config.metadata_mode == MetadataMode::Encryption;
4,359✔
84
            m_metadata_manager = std::make_unique<SyncMetadataManager>(m_file_manager->metadata_path(), encrypt,
4,359✔
85
                                                                       m_config.custom_encryption_key);
4,359✔
86

2,144✔
87
            REALM_ASSERT(m_metadata_manager);
4,359✔
88

2,144✔
89
            // Perform our "on next startup" actions such as deleting Realm files
2,144✔
90
            // which we couldn't delete immediately due to them being in use
2,144✔
91
            std::vector<SyncFileActionMetadata> completed_actions;
4,359✔
92
            SyncFileActionMetadataResults file_actions = m_metadata_manager->all_pending_actions();
4,359✔
93
            for (size_t i = 0; i < file_actions.size(); i++) {
4,409✔
94
                auto file_action = file_actions.get(i);
50✔
95
                if (run_file_action(file_action)) {
50✔
96
                    completed_actions.emplace_back(std::move(file_action));
46✔
97
                }
46✔
98
            }
50✔
99
            for (auto& action : completed_actions) {
2,167✔
100
                action.remove();
46✔
101
            }
46✔
102

2,144✔
103
            // Load persisted users into the users map.
2,144✔
104
            SyncUserMetadataResults users = m_metadata_manager->all_unmarked_users();
4,359✔
105
            for (size_t i = 0; i < users.size(); i++) {
4,373✔
106
                auto user_data = users.get(i);
14✔
107
                auto refresh_token = user_data.refresh_token();
14✔
108
                auto access_token = user_data.access_token();
14✔
109
                if (!refresh_token.empty() && !access_token.empty()) {
14✔
110
                    users_to_add.push_back(std::make_shared<SyncUser>(user_data, this));
12✔
111
                }
12✔
112
            }
14✔
113

2,144✔
114
            // Delete any users marked for death.
2,144✔
115
            std::vector<SyncUserMetadata> dead_users;
4,359✔
116
            SyncUserMetadataResults users_to_remove = m_metadata_manager->all_users_marked_for_removal();
4,359✔
117
            dead_users.reserve(users_to_remove.size());
4,359✔
118
            for (size_t i = 0; i < users_to_remove.size(); i++) {
4,363✔
119
                auto user = users_to_remove.get(i);
4✔
120
                // FIXME: delete user data in a different way? (This deletes a logged-out user's data as soon as the
2✔
121
                // app launches again, which might not be how some apps want to treat their data.)
2✔
122
                try {
4✔
123
                    m_file_manager->remove_user_realms(user.identity(), user.realm_file_paths());
4✔
124
                    dead_users.emplace_back(std::move(user));
4✔
125
                }
4✔
126
                catch (FileAccessError const&) {
2✔
127
                    continue;
×
128
                }
×
129
            }
4✔
130
            for (auto& user : dead_users) {
4,359✔
131
                user.remove();
4✔
132
            }
4✔
133
        }
4,359✔
134
    }
4,359✔
135
    {
4,359✔
136
        util::CheckedLockGuard lock(m_user_mutex);
4,359✔
137
        m_users.insert(m_users.end(), users_to_add.begin(), users_to_add.end());
4,359✔
138
    }
4,359✔
139
}
4,359✔
140

141
bool SyncManager::immediately_run_file_actions(const std::string& realm_path)
142
{
10✔
143
    util::CheckedLockGuard lock(m_file_system_mutex);
10✔
144
    if (!m_metadata_manager) {
10✔
145
        return false;
×
146
    }
×
147
    if (auto metadata = m_metadata_manager->get_file_action_metadata(realm_path)) {
10✔
148
        if (run_file_action(*metadata)) {
6✔
149
            metadata->remove();
6✔
150
            return true;
6✔
151
        }
6✔
152
    }
4✔
153
    return false;
4✔
154
}
4✔
155

156
// Perform a file action. Returns whether or not the file action can be removed.
157
bool SyncManager::run_file_action(SyncFileActionMetadata& md)
158
{
56✔
159
    switch (md.action()) {
56✔
160
        case SyncFileActionMetadata::Action::DeleteRealm:
14✔
161
            // Delete all the files for the given Realm.
7✔
162
            return m_file_manager->remove_realm(md.original_name());
14✔
163
        case SyncFileActionMetadata::Action::BackUpThenDeleteRealm:
42✔
164
            // Copy the primary Realm file to the recovery dir, and then delete the Realm.
21✔
165
            auto new_name = md.new_name();
42✔
166
            auto original_name = md.original_name();
42✔
167
            if (!util::File::exists(original_name)) {
42✔
168
                // The Realm file doesn't exist anymore.
9✔
169
                return true;
18✔
170
            }
18✔
171
            if (new_name && !util::File::exists(*new_name) &&
24✔
172
                m_file_manager->copy_realm_file(original_name, *new_name)) {
23✔
173
                // We successfully copied the Realm file to the recovery directory.
11✔
174
                bool did_remove = m_file_manager->remove_realm(original_name);
22✔
175
                // if the copy succeeded but not the delete, then running BackupThenDelete
11✔
176
                // a second time would fail, so change this action to just delete the original file.
11✔
177
                if (did_remove) {
22✔
178
                    return true;
20✔
179
                }
20✔
180
                md.set_action(SyncFileActionMetadata::Action::DeleteRealm);
2✔
181
                return false;
2✔
182
            }
2✔
183
            return false;
2✔
184
    }
×
185
    return false;
×
186
}
×
187

188
void SyncManager::reset_for_testing()
189
{
4,317✔
190
    {
4,317✔
191
        util::CheckedLockGuard lock(m_file_system_mutex);
4,317✔
192
        m_metadata_manager = nullptr;
4,317✔
193
    }
4,317✔
194

2,123✔
195
    {
4,317✔
196
        // Destroy all the users.
2,123✔
197
        util::CheckedLockGuard lock(m_user_mutex);
4,317✔
198
        for (auto& user : m_users) {
4,758✔
199
            user->detach_from_sync_manager();
4,758✔
200
        }
4,758✔
201
        m_users.clear();
4,317✔
202
        m_current_user = nullptr;
4,317✔
203
    }
4,317✔
204

2,123✔
205
    {
4,317✔
206
        util::CheckedLockGuard lock(m_mutex);
4,317✔
207
        // Stop the client. This will abort any uploads that inactive sessions are waiting for.
2,123✔
208
        if (m_sync_client)
4,317✔
209
            m_sync_client->stop();
4,317✔
210
    }
4,317✔
211

2,123✔
212
    {
4,317✔
213
        util::CheckedUniqueLock lock(m_session_mutex);
4,317✔
214

2,123✔
215
        bool no_sessions = !do_has_existing_sessions();
4,317✔
216
        // There's a race between this function and sessions tearing themselves down waiting for m_session_mutex.
2,123✔
217
        // So we give up to a 5 second grace period for any sessions being torn down to unregister themselves.
2,123✔
218
        auto since_poll_start = [start = std::chrono::steady_clock::now()] {
2,123✔
219
            return std::chrono::steady_clock::now() - start;
×
220
        };
×
221
        for (; !no_sessions && since_poll_start() < std::chrono::seconds(5);
4,317!
222
             no_sessions = !do_has_existing_sessions()) {
2,123✔
223
            lock.unlock();
×
224
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
×
225
            lock.lock();
×
226
        }
×
227
        // Callers of `SyncManager::reset_for_testing` should ensure there are no existing sessions
2,123✔
228
        // prior to calling `reset_for_testing`.
2,123✔
229
        if (!no_sessions) {
4,317✔
230
            util::CheckedLockGuard lock(m_mutex);
×
231
            for (auto session : m_sessions) {
×
232
                m_logger_ptr->error("open session at path '%1'", session.first);
×
233
            }
×
234
        }
×
235
        REALM_ASSERT_RELEASE(no_sessions);
4,317✔
236

2,123✔
237
        // Destroy any inactive sessions.
2,123✔
238
        // FIXME: We shouldn't have any inactive sessions at this point! Sessions are expected to
2,123✔
239
        // remain inactive until their final upload completes, at which point they are unregistered
2,123✔
240
        // and destroyed. Our call to `sync::Client::stop` above aborts all uploads, so all sessions
2,123✔
241
        // should have already been destroyed.
2,123✔
242
        m_sessions.clear();
4,317✔
243
    }
4,317✔
244

2,123✔
245
    {
4,317✔
246
        util::CheckedLockGuard lock(m_mutex);
4,317✔
247
        // Destroy the client now that we have no remaining sessions.
2,123✔
248
        m_sync_client = nullptr;
4,317✔
249

2,123✔
250
        // Reset even more state.
2,123✔
251
        m_config = {};
4,317✔
252
        m_logger_ptr.reset();
4,317✔
253
        m_sync_route = "";
4,317✔
254
    }
4,317✔
255

2,123✔
256
    {
4,317✔
257
        util::CheckedLockGuard lock(m_file_system_mutex);
4,317✔
258
        if (m_file_manager)
4,317✔
259
            util::try_remove_dir_recursive(m_file_manager->base_path());
4,317✔
260
        m_file_manager = nullptr;
4,317✔
261
    }
4,317✔
262
}
4,317✔
263

264
void SyncManager::set_log_level(util::Logger::Level level) noexcept
265
{
×
266
    util::CheckedLockGuard lock(m_mutex);
×
267
    m_config.log_level = level;
×
268
    // Update the level threshold in the already created logger
269
    if (m_logger_ptr) {
×
270
        m_logger_ptr->set_level_threshold(level);
×
271
    }
×
272
}
×
273

274
void SyncManager::set_logger_factory(SyncClientConfig::LoggerFactory factory)
275
{
×
276
    util::CheckedLockGuard lock(m_mutex);
×
277
    m_config.logger_factory = std::move(factory);
×
278

279
    if (m_sync_client)
×
NEW
280
        throw LogicError(ErrorCodes::IllegalOperation,
×
NEW
281
                         "Cannot set the logger factory after creating the sync client");
×
282

283
    // Create a new logger using the new factory
284
    do_make_logger();
×
285
}
×
286

287
void SyncManager::do_make_logger()
288
{
4,419✔
289
    if (m_config.logger_factory) {
4,419✔
290
        m_logger_ptr = m_config.logger_factory(m_config.log_level);
×
291
    }
×
292
    else {
4,419✔
293
        m_logger_ptr = util::Logger::get_default_logger();
4,419✔
294
    }
4,419✔
295
}
4,419✔
296

297
const std::shared_ptr<util::Logger>& SyncManager::get_logger() const
298
{
586✔
299
    util::CheckedLockGuard lock(m_mutex);
586✔
300
    return m_logger_ptr;
586✔
301
}
586✔
302

303
void SyncManager::set_user_agent(std::string user_agent)
304
{
×
305
    util::CheckedLockGuard lock(m_mutex);
×
306
    m_config.user_agent_application_info = std::move(user_agent);
×
307
}
×
308

309
void SyncManager::set_timeouts(SyncClientTimeouts timeouts)
310
{
×
311
    util::CheckedLockGuard lock(m_mutex);
×
312
    m_config.timeouts = timeouts;
×
313
}
×
314

315
void SyncManager::reconnect() const
316
{
2✔
317
    util::CheckedLockGuard lock(m_session_mutex);
2✔
318
    for (auto& it : m_sessions) {
1✔
319
        it.second->handle_reconnect();
×
320
    }
×
321
}
2✔
322

323
util::Logger::Level SyncManager::log_level() const noexcept
324
{
×
325
    util::CheckedLockGuard lock(m_mutex);
×
326
    return m_config.log_level;
×
327
}
×
328

329
bool SyncManager::perform_metadata_update(util::FunctionRef<void(SyncMetadataManager&)> update_function) const
330
{
10,337✔
331
    util::CheckedLockGuard lock(m_file_system_mutex);
10,337✔
332
    if (!m_metadata_manager) {
10,337✔
333
        return false;
93✔
334
    }
93✔
335
    update_function(*m_metadata_manager);
10,244✔
336
    return true;
10,244✔
337
}
10,244✔
338

339
std::shared_ptr<SyncUser> SyncManager::get_user(const std::string& user_id, const std::string& refresh_token,
340
                                                const std::string& access_token, const std::string& device_id)
341
{
8,388✔
342
    util::CheckedLockGuard lock(m_user_mutex);
8,388✔
343
    auto it = std::find_if(m_users.begin(), m_users.end(), [&](const auto& user) {
6,459✔
344
        return user->identity() == user_id && user->state() != SyncUser::State::Removed;
4,601✔
345
    });
4,601✔
346
    if (it == m_users.end()) {
8,388✔
347
        // No existing user.
2,367✔
348
        auto new_user = std::make_shared<SyncUser>(refresh_token, user_id, access_token, device_id, this);
4,810✔
349
        m_users.emplace(m_users.begin(), new_user);
4,810✔
350
        {
4,810✔
351
            util::CheckedLockGuard lock(m_file_system_mutex);
4,810✔
352
            // m_current_user is normally set very indirectly via the metadata manger
2,367✔
353
            if (!m_metadata_manager)
4,810✔
354
                m_current_user = new_user;
44✔
355
        }
4,810✔
356
        return new_user;
4,810✔
357
    }
4,810✔
358
    else { // LoggedOut => LoggedIn
3,578✔
359
        auto user = *it;
3,578✔
360
        REALM_ASSERT(user->state() != SyncUser::State::Removed);
3,578✔
361
        user->log_in(access_token, refresh_token);
3,578✔
362
        return user;
3,578✔
363
    }
3,578✔
364
}
8,388✔
365

366
std::vector<std::shared_ptr<SyncUser>> SyncManager::all_users()
367
{
250✔
368
    util::CheckedLockGuard lock(m_user_mutex);
250✔
369
    m_users.erase(std::remove_if(m_users.begin(), m_users.end(),
250✔
370
                                 [](auto& user) {
245✔
371
                                     bool should_remove = (user->state() == SyncUser::State::Removed);
240✔
372
                                     if (should_remove) {
240✔
373
                                         user->detach_from_sync_manager();
22✔
374
                                     }
22✔
375
                                     return should_remove;
240✔
376
                                 }),
240✔
377
                  m_users.end());
250✔
378
    return m_users;
250✔
379
}
250✔
380

381
std::shared_ptr<SyncUser> SyncManager::get_user_for_identity(std::string const& identity) const noexcept
382
{
1,117✔
383
    auto is_active_user = [identity](auto& el) {
1,125✔
384
        return el->identity() == identity;
1,125✔
385
    };
1,125✔
386
    auto it = std::find_if(m_users.begin(), m_users.end(), is_active_user);
1,117✔
387
    return it == m_users.end() ? nullptr : *it;
1,104✔
388
}
1,117✔
389

390
std::shared_ptr<SyncUser> SyncManager::get_current_user() const
391
{
991✔
392
    util::CheckedLockGuard lock(m_user_mutex);
991✔
393

486✔
394
    if (m_current_user)
991✔
395
        return m_current_user;
931✔
396
    util::CheckedLockGuard fs_lock(m_file_system_mutex);
60✔
397
    if (!m_metadata_manager)
60✔
398
        return nullptr;
×
399

30✔
400
    auto cur_user_ident = m_metadata_manager->get_current_user_identity();
60✔
401
    return cur_user_ident ? get_user_for_identity(*cur_user_ident) : nullptr;
49✔
402
}
60✔
403

404
void SyncManager::log_out_user(const SyncUser& user)
405
{
99✔
406
    util::CheckedLockGuard lock(m_user_mutex);
99✔
407

49✔
408
    // Move this user to the end of the vector
49✔
409
    auto user_pos = std::partition(m_users.begin(), m_users.end(), [&](auto& u) {
130✔
410
        return u.get() != &user;
130✔
411
    });
130✔
412

49✔
413
    auto active_user = std::find_if(m_users.begin(), user_pos, [](auto& u) {
64✔
414
        return u->state() == SyncUser::State::LoggedIn;
29✔
415
    });
29✔
416

49✔
417
    util::CheckedLockGuard fs_lock(m_file_system_mutex);
99✔
418
    bool was_active = m_current_user.get() == &user ||
99✔
419
                      (m_metadata_manager && m_metadata_manager->get_current_user_identity() == user.identity());
66✔
420
    if (!was_active)
99✔
421
        return;
3✔
422

48✔
423
    // Set the current active user to the next logged in user, or null if none
48✔
424
    if (active_user != user_pos) {
96✔
425
        m_current_user = *active_user;
16✔
426
        if (m_metadata_manager)
16✔
427
            m_metadata_manager->set_current_user_identity((*active_user)->identity());
16✔
428
    }
16✔
429
    else {
80✔
430
        m_current_user = nullptr;
80✔
431
        if (m_metadata_manager)
80✔
432
            m_metadata_manager->set_current_user_identity("");
72✔
433
    }
80✔
434
}
96✔
435

436
void SyncManager::set_current_user(const std::string& user_id)
437
{
1,048✔
438
    util::CheckedLockGuard lock(m_user_mutex);
1,048✔
439

513✔
440
    m_current_user = get_user_for_identity(user_id);
1,048✔
441
    util::CheckedLockGuard fs_lock(m_file_system_mutex);
1,048✔
442
    if (m_metadata_manager)
1,048✔
443
        m_metadata_manager->set_current_user_identity(user_id);
1,046✔
444
}
1,048✔
445

446
void SyncManager::remove_user(const std::string& user_id)
447
{
25✔
448
    util::CheckedLockGuard lock(m_user_mutex);
25✔
449
    if (auto user = get_user_for_identity(user_id))
25✔
450
        user->invalidate();
25✔
451
}
25✔
452

453
void SyncManager::delete_user(const std::string& user_id)
454
{
10✔
455
    util::CheckedLockGuard lock(m_user_mutex);
10✔
456
    // Avoid iterating over m_users twice by not calling `get_user_for_identity`.
5✔
457
    auto it = std::find_if(m_users.begin(), m_users.end(), [&user_id](auto& user) {
10✔
458
        return user->identity() == user_id;
10✔
459
    });
10✔
460
    auto user = it == m_users.end() ? nullptr : *it;
10✔
461

5✔
462
    if (!user)
10✔
463
        return;
×
464

5✔
465
    // Deletion should happen immediately, not when we do the cleanup
5✔
466
    // task on next launch.
5✔
467
    m_users.erase(it);
10✔
468
    user->detach_from_sync_manager();
10✔
469

5✔
470
    if (m_current_user && m_current_user->identity() == user->identity())
10✔
471
        m_current_user = nullptr;
10✔
472

5✔
473
    util::CheckedLockGuard fs_lock(m_file_system_mutex);
10✔
474
    if (!m_metadata_manager)
10✔
475
        return;
×
476

5✔
477
    auto users = m_metadata_manager->all_unmarked_users();
10✔
478
    for (size_t i = 0; i < users.size(); i++) {
12✔
479
        auto metadata = users.get(i);
12✔
480
        if (user->identity() == metadata.identity()) {
12✔
481
            m_file_manager->remove_user_realms(metadata.identity(), metadata.realm_file_paths());
10✔
482
            metadata.remove();
10✔
483
            break;
10✔
484
        }
10✔
485
    }
12✔
486
}
10✔
487

488
SyncManager::~SyncManager() NO_THREAD_SAFETY_ANALYSIS
489
{
4,421✔
490
    // Grab the current sessions under a lock so we can shut them down. We have to
2,175✔
491
    // release the lock before calling them as shutdown_and_wait() will call
2,175✔
492
    // back into us.
2,175✔
493
    decltype(m_sessions) current_sessions;
4,421✔
494
    {
4,421✔
495
        util::CheckedLockGuard lk(m_session_mutex);
4,421✔
496
        m_sessions.swap(current_sessions);
4,421✔
497
    }
4,421✔
498

2,175✔
499
    for (auto& [_, session] : current_sessions) {
2,176✔
500
        session->detach_from_sync_manager();
1✔
501
    }
1✔
502

2,175✔
503
    {
4,421✔
504
        util::CheckedLockGuard lk(m_user_mutex);
4,421✔
505
        for (auto& user : m_users) {
2,190✔
506
            user->detach_from_sync_manager();
30✔
507
        }
30✔
508
    }
4,421✔
509

2,175✔
510
    {
4,421✔
511
        util::CheckedLockGuard lk(m_mutex);
4,421✔
512
        // Stop the client. This will abort any uploads that inactive sessions are waiting for.
2,175✔
513
        if (m_sync_client)
4,421✔
514
            m_sync_client->stop();
40✔
515
    }
4,421✔
516
}
4,421✔
517

518
std::shared_ptr<SyncUser> SyncManager::get_existing_logged_in_user(const std::string& user_id) const
519
{
6✔
520
    util::CheckedLockGuard lock(m_user_mutex);
6✔
521
    auto user = get_user_for_identity(user_id);
6✔
522
    return user && user->state() == SyncUser::State::LoggedIn ? user : nullptr;
6✔
523
}
6✔
524

525
struct UnsupportedBsonPartition : public std::logic_error {
526
    UnsupportedBsonPartition(std::string msg)
527
        : std::logic_error(msg)
528
    {
×
529
    }
×
530
};
531

532
static std::string string_from_partition(const std::string& partition)
533
{
54✔
534
    bson::Bson partition_value = bson::parse(partition);
54✔
535
    switch (partition_value.type()) {
54✔
536
        case bson::Bson::Type::Int32:
2✔
537
            return util::format("i_%1", static_cast<int32_t>(partition_value));
2✔
538
        case bson::Bson::Type::Int64:
2✔
539
            return util::format("l_%1", static_cast<int64_t>(partition_value));
2✔
540
        case bson::Bson::Type::String:
44✔
541
            return util::format("s_%1", static_cast<std::string>(partition_value));
44✔
542
        case bson::Bson::Type::ObjectId:
2✔
543
            return util::format("o_%1", static_cast<ObjectId>(partition_value).to_string());
2✔
544
        case bson::Bson::Type::Uuid:
2✔
545
            return util::format("u_%1", static_cast<UUID>(partition_value).to_string());
2✔
546
        case bson::Bson::Type::Null:
2✔
547
            return "null";
2✔
548
        default:
✔
549
            throw UnsupportedBsonPartition(util::format("Unsupported partition key value: '%1'. Only int, string "
×
550
                                                        "UUID and ObjectId types are currently supported.",
×
551
                                                        partition_value.to_string()));
×
552
    }
54✔
553
}
54✔
554

555
std::string SyncManager::path_for_realm(const SyncConfig& config, util::Optional<std::string> custom_file_name) const
556
{
74✔
557
    auto user = config.user;
74✔
558
    REALM_ASSERT(user);
74✔
559
    std::string path;
74✔
560
    {
74✔
561
        util::CheckedLockGuard lock(m_file_system_mutex);
74✔
562
        REALM_ASSERT(m_file_manager);
74✔
563

37✔
564
        // Attempt to make a nicer filename which will ease debugging when
37✔
565
        // locating files in the filesystem.
37✔
566
        auto file_name = [&]() -> std::string {
74✔
567
            if (custom_file_name) {
74✔
568
                return *custom_file_name;
18✔
569
            }
18✔
570
            if (config.flx_sync_requested) {
56✔
571
                REALM_ASSERT_DEBUG(config.partition_value.empty());
2✔
572
                return "flx_sync_default";
2✔
573
            }
2✔
574
            return string_from_partition(config.partition_value);
54✔
575
        }();
54✔
576
        path = m_file_manager->realm_file_path(user->identity(), user->legacy_identities(), file_name,
74✔
577
                                               config.partition_value);
74✔
578
    }
74✔
579
    // Report the use of a Realm for this user, so the metadata can track it for clean up.
37✔
580
    perform_metadata_update([&](const auto& manager) {
60✔
581
        auto metadata = manager.get_or_make_user_metadata(user->identity());
46✔
582
        metadata->add_realm_file_path(path);
46✔
583
    });
46✔
584
    return path;
74✔
585
}
74✔
586

587
std::string SyncManager::recovery_directory_path(util::Optional<std::string> const& custom_dir_name) const
588
{
76✔
589
    util::CheckedLockGuard lock(m_file_system_mutex);
76✔
590
    REALM_ASSERT(m_file_manager);
76✔
591
    return m_file_manager->recovery_directory_path(custom_dir_name);
76✔
592
}
76✔
593

594
std::vector<std::shared_ptr<SyncSession>> SyncManager::get_all_sessions() const
595
{
52✔
596
    util::CheckedLockGuard lock(m_session_mutex);
52✔
597
    std::vector<std::shared_ptr<SyncSession>> sessions;
52✔
598
    for (auto& [_, session] : m_sessions) {
102✔
599
        if (auto external_reference = session->existing_external_reference())
102✔
600
            sessions.push_back(std::move(external_reference));
100✔
601
    }
102✔
602
    return sessions;
52✔
603
}
52✔
604

605
std::shared_ptr<SyncSession> SyncManager::get_existing_active_session(const std::string& path) const
606
{
×
607
    util::CheckedLockGuard lock(m_session_mutex);
×
608
    if (auto session = get_existing_session_locked(path)) {
×
609
        if (auto external_reference = session->existing_external_reference())
×
610
            return external_reference;
×
611
    }
×
612
    return nullptr;
×
613
}
×
614

615
std::shared_ptr<SyncSession> SyncManager::get_existing_session_locked(const std::string& path) const
616
{
2,803✔
617
    auto it = m_sessions.find(path);
2,803✔
618
    return it == m_sessions.end() ? nullptr : it->second;
2,694✔
619
}
2,803✔
620

621
std::shared_ptr<SyncSession> SyncManager::get_existing_session(const std::string& path) const
622
{
1,416✔
623
    util::CheckedLockGuard lock(m_session_mutex);
1,416✔
624
    if (auto session = get_existing_session_locked(path))
1,416✔
625
        return session->external_reference();
210✔
626

524✔
627
    return nullptr;
1,206✔
628
}
1,206✔
629

630
std::shared_ptr<SyncSession> SyncManager::get_session(std::shared_ptr<DB> db, const RealmConfig& config)
631
{
1,387✔
632
    auto& client = get_sync_client(); // Throws
1,387✔
633
#ifndef __EMSCRIPTEN__
1,387✔
634
    auto path = db->get_path();
1,387✔
635
    REALM_ASSERT_EX(path == config.path, path, config.path);
1,387✔
636
#else
637
    auto path = config.path;
638
#endif
639
    REALM_ASSERT(config.sync_config);
1,387✔
640

607✔
641
    util::CheckedUniqueLock lock(m_session_mutex);
1,387✔
642
    if (auto session = get_existing_session_locked(path)) {
1,387✔
643
        config.sync_config->user->register_session(session);
8✔
644
        return session->external_reference();
8✔
645
    }
8✔
646

603✔
647
    auto shared_session = SyncSession::create(client, std::move(db), config, this);
1,379✔
648
    m_sessions[path] = shared_session;
1,379✔
649

603✔
650
    // Create the external reference immediately to ensure that the session will become
603✔
651
    // inactive if an exception is thrown in the following code.
603✔
652
    auto external_reference = shared_session->external_reference();
1,379✔
653
    // unlocking m_session_mutex here prevents a deadlock for synchronous network
603✔
654
    // transports such as the unit test suite, in the case where the log in request is
603✔
655
    // denied by the server: Active -> WaitingForAccessToken -> handle_refresh(401
603✔
656
    // error) -> user.log_out() -> unregister_session (locks m_session_mutex again)
603✔
657
    lock.unlock();
1,379✔
658
    config.sync_config->user->register_session(std::move(shared_session));
1,379✔
659

603✔
660
    return external_reference;
1,379✔
661
}
1,379✔
662

663
bool SyncManager::has_existing_sessions()
664
{
12✔
665
    util::CheckedLockGuard lock(m_session_mutex);
12✔
666
    return do_has_existing_sessions();
12✔
667
}
12✔
668

669
bool SyncManager::do_has_existing_sessions()
670
{
4,329✔
671
    return std::any_of(m_sessions.begin(), m_sessions.end(), [](auto& element) {
2,149✔
672
        return element.second->existing_external_reference();
41✔
673
    });
41✔
674
}
4,329✔
675

676
void SyncManager::wait_for_sessions_to_terminate()
677
{
58✔
678
    auto& client = get_sync_client(); // Throws
58✔
679
    client.wait_for_session_terminations();
58✔
680
}
58✔
681

682
void SyncManager::unregister_session(const std::string& path)
683
{
2,097✔
684
    util::CheckedUniqueLock lock(m_session_mutex);
2,097✔
685
    auto it = m_sessions.find(path);
2,097✔
686
    if (it == m_sessions.end()) {
2,097✔
687
        // The session may already be unregistered. This always happens in the
688
        // SyncManager destructor, and can also happen due to multiple threads
689
        // tearing things down at once.
690
        return;
2✔
691
    }
2✔
692

896✔
693
    // Sync session teardown calls this function, so we need to be careful with
896✔
694
    // locking here. We need to unlock `m_session_mutex` before we do anything
896✔
695
    // which could result in a re-entrant call or we'll deadlock, which in this
896✔
696
    // function means unlocking before we destroy a `shared_ptr<SyncSession>`
896✔
697
    // (either the external reference or internal reference versions).
896✔
698
    // The external reference version will only be the final reference if
896✔
699
    // another thread drops a reference while we're in this function.
896✔
700
    // Dropping the final internal reference does not appear to ever actually
896✔
701
    // result in a recursive call to this function at the time this comment was
896✔
702
    // written, but releasing the lock in that case as well is still safer.
896✔
703

896✔
704
    if (auto existing_session = it->second->existing_external_reference()) {
2,095✔
705
        // We got here because the session entered the inactive state, but
312✔
706
        // there's still someone referencing it so we should leave it be. This
312✔
707
        // can happen if the user was logged out, or if all Realms using the
312✔
708
        // session were destroyed but the SDK user is holding onto the session.
312✔
709

312✔
710
        // Explicit unlock so that `existing_session`'s destructor runs after
312✔
711
        // the unlock for the reasons noted above
312✔
712
        lock.unlock();
756✔
713
        return;
756✔
714
    }
756✔
715

584✔
716
    // Remove the session from the map while holding the lock, but then defer
584✔
717
    // destroying it until after we unlock the mutex for the reasons noted above.
584✔
718
    auto session = m_sessions.extract(it);
1,339✔
719
    lock.unlock();
1,339✔
720
}
1,339✔
721

722
void SyncManager::set_session_multiplexing(bool allowed)
723
{
4✔
724
    util::CheckedLockGuard lock(m_mutex);
4✔
725
    if (m_config.multiplex_sessions == allowed)
4✔
726
        return; // Already enabled, we can ignore
2✔
727

1✔
728
    if (m_sync_client)
2✔
NEW
729
        throw LogicError(ErrorCodes::IllegalOperation,
×
NEW
730
                         "Cannot enable session multiplexing after creating the sync client");
×
731

1✔
732
    m_config.multiplex_sessions = allowed;
2✔
733
}
2✔
734

735
SyncClient& SyncManager::get_sync_client() const
736
{
5,804✔
737
    util::CheckedLockGuard lock(m_mutex);
5,804✔
738
    if (!m_sync_client)
5,804✔
739
        m_sync_client = create_sync_client(); // Throws
4,357✔
740
    return *m_sync_client;
5,804✔
741
}
5,804✔
742

743
std::unique_ptr<SyncClient> SyncManager::create_sync_client() const
744
{
4,357✔
745
    return std::make_unique<SyncClient>(m_logger_ptr, m_config, weak_from_this());
4,357✔
746
}
4,357✔
747

748
util::Optional<SyncAppMetadata> SyncManager::app_metadata() const
749
{
5,503✔
750
    util::CheckedLockGuard lock(m_file_system_mutex);
5,503✔
751
    if (!m_metadata_manager) {
5,503✔
752
        return util::none;
68✔
753
    }
68✔
754
    return m_metadata_manager->get_app_metadata();
5,435✔
755
}
5,435✔
756

757
void SyncManager::close_all_sessions()
758
{
×
759
    // log_out() will call unregister_session(), which requires m_session_mutex,
760
    // so we need to iterate over them without holding the lock.
761
    decltype(m_sessions) sessions;
×
762
    {
×
763
        util::CheckedLockGuard lk(m_session_mutex);
×
764
        m_sessions.swap(sessions);
×
765
    }
×
766

767
    for (auto& [_, session] : sessions) {
×
768
        session->force_close();
×
769
    }
×
770

771
    get_sync_client().wait_for_session_terminations();
×
772
}
×
773

774
void SyncManager::OnlyForTesting::voluntary_disconnect_all_connections(SyncManager& mgr)
775
{
6✔
776
    mgr.get_sync_client().voluntary_disconnect_all_connections();
6✔
777
}
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

© 2026 Coveralls, Inc