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

realm / realm-core / thomas.goyne_275

09 Apr 2024 03:33AM UTC coverage: 92.608% (+0.5%) from 92.088%
thomas.goyne_275

Pull #7300

Evergreen

tgoyne
Extract some duplicated code in PushClient
Pull Request #7300: Rework sync user handling and metadata storage

102672 of 194970 branches covered (52.66%)

3165 of 3247 new or added lines in 46 files covered. (97.47%)

34 existing lines in 9 files now uncovered.

249420 of 269329 relevant lines covered (92.61%)

45087511.34 hits per line

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

98.82
/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::default_connect_timeout)
39
    , connection_linger_time(sync::default_connection_linger_time)
40
    , ping_keepalive_period(sync::default_ping_keepalive_period)
41
    , pong_keepalive_timeout(sync::default_pong_keepalive_timeout)
42
    , fast_reconnect_limit(sync::default_fast_reconnect_limit)
43
{
37,564✔
44
}
37,564✔
45

46
std::shared_ptr<SyncManager> SyncManager::create(std::shared_ptr<app::App> app, std::string sync_route,
47
                                                 const SyncClientConfig& config, const std::string& app_id)
4,457✔
48
{
37,028✔
49
    return std::make_shared<SyncManager>(Private(), std::move(app), sync_route, config, app_id);
37,028✔
50
}
32,571✔
51

52
SyncManager::SyncManager(Private, std::shared_ptr<app::App> app, std::string sync_route,
53
                         const SyncClientConfig& config, const std::string& app_id)
4,457✔
54
    : m_config(config)
2,193✔
55
    , m_file_manager(std::make_unique<SyncFileManager>(m_config.base_file_path, app_id))
2,193✔
56
    , m_sync_route(sync_route)
4,457✔
57
    , m_app(app)
4,457✔
58
    , m_app_id(app_id)
59
{
32,571✔
60
    // create the initial logger - if the logger_factory is updated later, a new
20,462✔
61
    // logger will be created at that time.
20,462✔
62
    do_make_logger();
34,748✔
63

20,462✔
64
    if (m_config.metadata_mode == MetadataMode::NoMetadata) {
36,996✔
65
        return;
29,771✔
66
    }
32,019✔
67

6,854✔
68
    bool encrypt = m_config.metadata_mode == MetadataMode::Encryption;
9,402✔
69
    m_metadata_manager = std::make_unique<SyncMetadataManager>(m_file_manager->metadata_path(), encrypt,
6,790✔
70
                                                               m_config.custom_encryption_key);
9,402✔
71

6,854✔
72
    m_metadata_manager->perform_launch_actions(*m_file_manager);
6,790✔
73

6,875✔
74
    // Load persisted users into the users map.
4,301✔
75
    for (auto user : m_metadata_manager->all_logged_in_users()) {
4,427✔
76
        m_users.push_back(std::make_shared<SyncUser>(SyncUser::Private(), user, this));
3,140✔
77
    }
252✔
78
}
4,266✔
79

4,439!
80
bool SyncManager::immediately_run_file_actions(const std::string& realm_path)
2,191✔
81
{
98✔
82
    util::CheckedLockGuard lock(m_file_system_mutex);
98✔
83
    if (m_metadata_manager) {
98✔
84
        return m_metadata_manager->perform_file_actions(*m_file_manager, realm_path);
84✔
85
    }
2,261✔
86
    return false;
2,177✔
87
}
4,425✔
88

4,489✔
89
void SyncManager::tear_down_for_testing()
4,489!
90
{
29,143✔
91
    close_all_sessions();
31,423✔
92

17,743✔
93
    {
35,848✔
94
        util::CheckedLockGuard lock(m_file_system_mutex);
33,600✔
95
        m_metadata_manager = nullptr;
31,320✔
96
    }
33,600✔
97

17,640✔
98
    {
33,600✔
99
        // Destroy all the users.
20,377✔
100
        util::CheckedLockGuard lock(m_user_mutex);
36,305✔
101
        for (auto& user : m_users) {
39,047✔
102
            user->detach_from_sync_manager();
36,342✔
103
        }
38,590✔
104
        m_users.clear();
35,848✔
105
        m_current_user = nullptr;
31,320✔
106
    }
35,848✔
107

22,168✔
108
    {
33,568✔
109
        util::CheckedLockGuard lock(m_mutex);
35,848✔
110
        // Stop the client. This will abort any uploads that inactive sessions are waiting for.
17,743✔
111
        if (m_sync_client)
31,423✔
112
            m_sync_client->stop();
29,143✔
113
    }
31,423✔
114

17,743✔
115
    {
29,143✔
116
        util::CheckedUniqueLock lock(m_session_mutex);
31,423!
117

15,463✔
118
        bool no_sessions = !do_has_existing_sessions();
29,143✔
119
        // There's a race between this function and sessions tearing themselves down waiting for m_session_mutex.
15,463✔
120
        // So we give up to a 5 second grace period for any sessions being torn down to unregister themselves.
13,254✔
121
        auto since_poll_start = [start = std::chrono::steady_clock::now()] {
13,254✔
122
            return std::chrono::steady_clock::now() - start;
4,489!
123
        };
2,209✔
124
        for (; !no_sessions && since_poll_start() < std::chrono::seconds(5);
26,934!
125
             no_sessions = !do_has_existing_sessions()) {
13,254✔
126
            lock.unlock();
×
UNCOV
127
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
×
128
            lock.lock();
2,209✔
129
        }
2,209✔
130
        // Callers of `SyncManager::tear_down_for_testing` should ensure there are no existing sessions
17,743✔
131
        // prior to calling `tear_down_for_testing`.
13,254✔
132
        if (!no_sessions) {
26,934✔
133
            util::CheckedLockGuard lock(m_mutex);
134
            for (auto session : m_sessions) {
×
135
                m_logger_ptr->error("open session at path '%1'", session.first);
4,457✔
136
            }
8,946✔
137
        }
2,209✔
138
        REALM_ASSERT_RELEASE(no_sessions);
29,143✔
139

19,920✔
140
        // Destroy any inactive sessions.
19,920✔
141
        // FIXME: We shouldn't have any inactive sessions at this point! Sessions are expected to
19,920✔
142
        // remain inactive until their final upload completes, at which point they are unregistered
19,920✔
143
        // and destroyed. Our call to `sync::Client::stop` above aborts all uploads, so all sessions
22,200✔
144
        // should have already been destroyed.
17,743✔
145
        m_sessions.clear();
29,143✔
146
    }
35,669✔
147

21,989✔
148
    {
33,389✔
149
        util::CheckedLockGuard lock(m_mutex);
35,669✔
150
        // Destroy the client now that we have no remaining sessions.
17,743✔
151
        m_sync_client = nullptr;
31,423✔
152
        m_logger_ptr.reset();
29,143✔
153
    }
31,423✔
154

17,743✔
155
    {
31,423✔
156
        util::CheckedLockGuard lock(m_file_system_mutex);
31,423✔
157
        if (m_file_manager)
31,423✔
158
            util::try_remove_dir_recursive(m_file_manager->base_path());
31,423✔
159
        m_file_manager = nullptr;
31,423✔
160
    }
26,934✔
161
}
26,934✔
162

163
void SyncManager::set_log_level(util::Logger::Level level) noexcept
UNCOV
164
{
×
165
    util::CheckedLockGuard lock(m_mutex);
UNCOV
166
    m_config.log_level = level;
×
167
    // Update the level threshold in the already created logger
UNCOV
168
    if (m_logger_ptr) {
×
UNCOV
169
        m_logger_ptr->set_level_threshold(level);
×
170
    }
171
}
172

173
void SyncManager::set_logger_factory(SyncClientConfig::LoggerFactory factory)
174
{
×
175
    util::CheckedLockGuard lock(m_mutex);
176
    m_config.logger_factory = std::move(factory);
×
177

178
    if (m_sync_client)
4,457!
179
        throw LogicError(ErrorCodes::IllegalOperation,
2,193✔
180
                         "Cannot set the logger factory after creating the sync client");
2,193✔
181

2,193✔
182
    // Create a new logger using the new factory
4,457✔
183
    do_make_logger();
4,457✔
184
}
4,457✔
185

9,110✔
186
void SyncManager::do_make_logger()
9,110✔
187
{
30,149✔
188
    if (m_config.logger_factory) {
30,149✔
189
        m_logger_ptr = m_config.logger_factory(m_config.log_level);
4,843✔
190
    }
4,843✔
191
    else {
34,498✔
192
        m_logger_ptr = util::Logger::get_default_logger();
36,800✔
193
    }
32,147✔
194
}
30,111✔
195

5,163✔
196
const std::shared_ptr<util::Logger>& SyncManager::get_logger() const
708✔
197
{
9,399✔
198
    util::CheckedLockGuard lock(m_mutex);
9,399✔
199
    return m_logger_ptr;
4,236✔
200
}
4,236✔
201

52✔
202
void SyncManager::set_user_agent(std::string user_agent)
52✔
203
{
52✔
204
    util::CheckedLockGuard lock(m_mutex);
101✔
205
    m_config.user_agent_application_info = std::move(user_agent);
101✔
206
}
99✔
207

101✔
208
void SyncManager::set_timeouts(SyncClientTimeouts timeouts)
52✔
209
{
52✔
210
    util::CheckedLockGuard lock(m_mutex);
211
    m_config.timeouts = timeouts;
212
}
14,121✔
213

14,123✔
214
void SyncManager::reconnect() const
14,123✔
215
{
7,161✔
216
    util::CheckedLockGuard lock(m_session_mutex);
250✔
217
    for (auto& it : m_sessions) {
105✔
218
        it.second->handle_reconnect();
101✔
219
    }
99✔
220
}
250✔
221

14,121✔
222
util::Logger::Level SyncManager::log_level() const noexcept
14,121✔
223
{
224
    util::CheckedLockGuard lock(m_mutex);
225
    return m_config.log_level;
226
}
227

6,676!
228
bool SyncManager::perform_metadata_update(util::FunctionRef<void(SyncMetadataManager&)> update_function) const
6,676!
229
{
46,732✔
230
    util::CheckedLockGuard lock(m_file_system_mutex);
44,242✔
231
    if (!m_metadata_manager) {
44,242✔
232
        return false;
27,606✔
233
    }
27,606✔
234
    update_function(*m_metadata_manager);
17,430✔
235
    return true;
18,119✔
236
}
18,119✔
237

3,063✔
238
std::shared_ptr<SyncUser> SyncManager::get_user(const std::string& user_id, const std::string& refresh_token,
8,307✔
239
                                                const std::string& access_token, const std::string& device_id)
5,128✔
240
{
35,896✔
241
    std::shared_ptr<SyncUser> user;
37,500✔
242
    {
35,491✔
243
        util::CheckedLockGuard lock(m_user_mutex);
33,553✔
244
        auto it = std::find_if(m_users.begin(), m_users.end(), [&](const auto& user) {
20,118✔
245
            return user->identity() == user_id && user->state() != SyncUser::State::Removed;
12,827✔
246
        });
10,947✔
247
        if (it == m_users.end()) {
37,185✔
248
            // No existing user.
19,916✔
249
            auto new_user = std::make_shared<SyncUser>(SyncUser::Private(), refresh_token, user_id, access_token,
35,252✔
250
                                                       device_id, this);
36,827✔
251
            m_users.emplace(m_users.begin(), new_user);
36,827✔
252
            {
34,271✔
253
                util::CheckedLockGuard lock(m_file_system_mutex);
36,827✔
254
                // m_current_user is normally set very indirectly via the metadata manger
20,371✔
255
                if (!m_metadata_manager)
35,252✔
256
                    m_current_user = new_user;
28,532✔
257
            }
35,252✔
258
            return new_user;
31,835✔
259
        }
30,962✔
260

1,931✔
261
        // LoggedOut => LoggedIn
1,931✔
262
        user = *it;
652✔
263
        REALM_ASSERT(user->state() != SyncUser::State::Removed);
652✔
264
    }
1,342✔
265
    user->log_in(access_token, refresh_token);
2,211✔
266
    return user;
2,119✔
267
}
1,250✔
268

946✔
269
std::vector<std::shared_ptr<SyncUser>> SyncManager::all_users()
946✔
270
{
3,303✔
271
    util::CheckedLockGuard lock(m_user_mutex);
3,296✔
272
    m_users.erase(std::remove_if(m_users.begin(), m_users.end(),
1,722✔
273
                                 [](auto& user) {
1,680✔
274
                                     bool should_remove = (user->state() == SyncUser::State::Removed);
1,439✔
275
                                     if (should_remove) {
1,439✔
276
                                         user->detach_from_sync_manager();
379✔
277
                                     }
379✔
278
                                     return should_remove;
1,652✔
279
                                 }),
1,652✔
280
                  m_users.end());
6,174✔
281
    return m_users;
3,672✔
282
}
1,490✔
283

1,261✔
284
std::shared_ptr<SyncUser> SyncManager::get_user_for_identity(std::string const& identity) const noexcept
5,707✔
285
{
8,823✔
286
    auto is_active_user = [identity](auto& el) {
8,883✔
287
        return el->identity() == identity;
8,931✔
288
    };
8,919✔
289
    auto it = std::find_if(m_users.begin(), m_users.end(), is_active_user);
8,871✔
290
    return it == m_users.end() ? nullptr : *it;
7,540✔
291
}
7,554✔
292

4,678✔
293
std::shared_ptr<SyncUser> SyncManager::get_current_user() const
7,174✔
294
{
32,890✔
295
    util::CheckedLockGuard lock(m_user_mutex);
35,242✔
296

21,068✔
297
    if (m_current_user)
28,159✔
298
        return m_current_user;
27,787✔
299
    util::CheckedLockGuard fs_lock(m_file_system_mutex);
421✔
300
    if (!m_metadata_manager)
445✔
301
        return nullptr;
214✔
302

1,234✔
303
    auto cur_user_ident = m_metadata_manager->get_current_user_identity();
1,360✔
304
    return cur_user_ident ? get_user_for_identity(*cur_user_ident) : nullptr;
1,318✔
305
}
1,318✔
306

1,161✔
307
void SyncManager::log_out_user(const SyncUser& user)
1,161✔
308
{
1,683✔
309
    util::CheckedLockGuard lock(m_user_mutex);
1,683✔
310

1,474✔
311
    // Move this user to the end of the vector
1,474✔
312
    auto user_pos = std::partition(m_users.begin(), m_users.end(), [&](auto& u) {
1,948✔
313
        return u.get() != &user;
1,869✔
314
    });
3,262✔
315

709✔
316
    auto active_user = std::find_if(m_users.begin(), user_pos, [](auto& u) {
799✔
317
        return u->state() == SyncUser::State::LoggedIn;
619✔
318
    });
667✔
319

775✔
320
    util::CheckedLockGuard fs_lock(m_file_system_mutex);
1,021✔
321
    bool was_active = m_current_user.get() == &user ||
1,063✔
322
                      (m_metadata_manager && m_metadata_manager->get_current_user_identity() == user.identity());
1,252✔
323
    if (!was_active)
1,547✔
324
        return;
995✔
325

1,036✔
326
    // Set the current active user to the next logged in user, or null if none
960✔
327
    if (active_user != user_pos) {
1,236✔
328
        m_current_user = *active_user;
1,617✔
329
        if (m_metadata_manager)
1,625✔
330
            m_metadata_manager->set_current_user_identity((*active_user)->identity());
1,637✔
331
    }
172✔
332
    else {
532✔
333
        m_current_user = nullptr;
490✔
334
        if (m_metadata_manager)
14,633✔
335
            m_metadata_manager->set_current_user_identity("");
14,397✔
336
    }
14,557✔
337
}
7,570✔
338

15,287✔
339
void SyncManager::set_current_user(const std::string& user_id)
15,287✔
340
{
12,318✔
341
    util::CheckedLockGuard lock(m_user_mutex);
8,320✔
342

4,696✔
343
    m_current_user = get_user_for_identity(user_id);
17,565✔
344
    util::CheckedLockGuard fs_lock(m_file_system_mutex);
13,066✔
345
    if (m_metadata_manager)
8,302✔
346
        m_metadata_manager->set_current_user_identity(user_id);
6,672✔
347
}
7,116✔
348

25✔
349
void SyncManager::remove_user(const std::string& user_id)
4,863✔
350
{
231✔
351
    util::CheckedLockGuard lock(m_user_mutex);
206✔
352
    if (auto user = get_user_for_identity(user_id))
206✔
353
        user->invalidate();
232✔
354
}
14,251✔
355

10✔
356
void SyncManager::delete_user(const std::string& user_id)
10✔
357
{
69✔
358
    util::CheckedLockGuard lock(m_user_mutex);
74✔
359
    // Avoid iterating over m_users twice by not calling `get_user_for_identity`.
44✔
360
    auto it = std::find_if(m_users.begin(), m_users.end(), [&user_id](auto& user) {
72✔
361
        return user->identity() == user_id;
71✔
362
    });
67✔
363
    auto user = it == m_users.end() ? nullptr : *it;
70✔
364

30✔
365
    if (!user)
66✔
366
        return;
7✔
367

37✔
368
    // Deletion should happen immediately, not when we do the cleanup
40✔
369
    // task on next launch.
40✔
370
    m_users.erase(it);
6,860✔
371
    user->detach_from_sync_manager();
6,865✔
372

6,835✔
373
    if (m_current_user && m_current_user->identity() == user->identity())
4,492✔
374
        m_current_user = nullptr;
6,865✔
375

6,835✔
376
    util::CheckedLockGuard fs_lock(m_file_system_mutex);
60✔
377
    if (!m_metadata_manager)
65✔
378
        return;
4,437✔
379

4,469✔
380
    auto users = m_metadata_manager->all_unmarked_users();
4,499✔
381
    for (size_t i = 0; i < users.size(); i++) {
4,511✔
382
        auto metadata = users.get(i);
82✔
383
        if (user->identity() == metadata.identity()) {
82✔
384
            m_file_manager->remove_user_realms(metadata.identity(), metadata.realm_file_paths());
4,495✔
385
            metadata.remove();
2,247✔
386
            break;
2,249✔
387
        }
4,495✔
388
    }
4,497✔
389
}
4,485✔
390

9,078✔
391
SyncManager::~SyncManager() NO_THREAD_SAFETY_ANALYSIS
6,716✔
392
{
32,386✔
393
    // Grab the current sessions under a lock so we can shut them down. We have to
18,238✔
394
    // release the lock before calling them as shutdown_and_wait() will call
18,453✔
395
    // back into us.
18,453✔
396
    decltype(m_sessions) current_sessions;
34,748✔
397
    {
36,996✔
398
        util::CheckedLockGuard lk(m_session_mutex);
36,996✔
399
        m_sessions.swap(current_sessions);
30,209✔
400
    }
30,216✔
401

22,454✔
402
    for (auto& [_, session] : current_sessions) {
22,492✔
403
        session->detach_from_sync_manager();
11,061✔
404
    }
13,423✔
405

27,093✔
406
    {
38,952✔
407
        util::CheckedLockGuard lk(m_user_mutex);
36,710✔
408
        for (auto& user : m_users) {
22,832✔
409
            user->detach_from_sync_manager();
5,241✔
410
        }
2,879✔
411
    }
32,571✔
412

18,399✔
413
    {
30,209✔
414
        util::CheckedLockGuard lk(m_mutex);
32,571✔
415
        // Stop the client. This will abort any uploads that inactive sessions are waiting for.
13,800✔
416
        if (m_sync_client)
32,571✔
417
            m_sync_client->stop();
4,977✔
418
    }
27,918✔
419
}
27,918✔
420

12✔
421
std::shared_ptr<SyncUser> SyncManager::get_existing_logged_in_user(const std::string& user_id) const
12✔
422
{
48✔
423
    util::CheckedLockGuard lock(m_user_mutex);
42✔
424
    auto user = get_user_for_identity(user_id);
42✔
425
    return user && user->state() == SyncUser::State::LoggedIn ? user : nullptr;
36✔
426
}
36✔
427

428
struct UnsupportedBsonPartition : public std::logic_error {
429
    UnsupportedBsonPartition(std::string msg)
430
        : std::logic_error(msg)
431
    {
432
    }
433
};
434

54✔
435
static std::string string_from_partition(const std::string& partition)
54✔
436
{
378✔
437
    bson::Bson partition_value = bson::parse(partition);
326✔
438
    switch (partition_value.type()) {
326✔
439
        case bson::Bson::Type::Int32:
14✔
440
            return util::format("i_%1", static_cast<int32_t>(partition_value));
14✔
441
        case bson::Bson::Type::Int64:
56✔
442
            return util::format("l_%1", static_cast<int64_t>(partition_value));
56✔
443
        case bson::Bson::Type::String:
266✔
444
            return util::format("s_%1", static_cast<std::string>(partition_value));
266✔
445
        case bson::Bson::Type::ObjectId:
14✔
446
            return util::format("o_%1", static_cast<ObjectId>(partition_value).to_string());
14✔
447
        case bson::Bson::Type::Uuid:
14✔
448
            return util::format("u_%1", static_cast<UUID>(partition_value).to_string());
14✔
449
        case bson::Bson::Type::Null:
12✔
450
            return "null";
12✔
451
        default:
12✔
452
            throw UnsupportedBsonPartition(util::format("Unsupported partition key value: '%1'. Only int, string "
453
                                                        "UUID and ObjectId types are currently supported.",
54✔
454
                                                        partition_value.to_string()));
54✔
455
    }
324✔
456
}
324✔
457

72✔
458
std::string SyncManager::path_for_realm(const SyncConfig& config, util::Optional<std::string> custom_file_name) const
72✔
459
{
504✔
460
    auto user = config.user;
504✔
461
    REALM_ASSERT(user);
504✔
462
    std::string path;
504✔
463
    {
504✔
464
        util::CheckedLockGuard lock(m_file_system_mutex);
468✔
465
        REALM_ASSERT(m_file_manager);
468✔
466

252✔
467
        // Attempt to make a nicer filename which will ease debugging when
288✔
468
        // locating files in the filesystem.
288✔
469
        auto file_name = [&]() -> std::string {
448✔
470
            if (custom_file_name) {
448✔
471
                return *custom_file_name;
152✔
472
            }
98✔
473
            if (config.flx_sync_requested) {
338✔
474
                REALM_ASSERT_DEBUG(config.partition_value.empty());
14✔
475
                return "flx_sync_default";
66✔
476
            }
66✔
477
            return string_from_partition(config.partition_value);
396✔
478
        }();
396✔
479
        path = m_file_manager->realm_file_path(user->identity(), user->legacy_identities(), file_name,
504✔
480
                                               config.partition_value);
468✔
481
    }
490✔
482
    // Report the use of a Realm for this user, so the metadata can track it for clean up.
260✔
483
    perform_metadata_update([&](const auto& manager) {
392✔
484
        auto metadata = manager.get_or_make_user_metadata(user->identity());
308✔
485
        metadata->add_realm_file_path(path);
336✔
486
    });
336✔
487
    return path;
432✔
488
}
432✔
489

80✔
490
std::string SyncManager::recovery_directory_path(util::Optional<std::string> const& custom_dir_name) const
80✔
491
{
560✔
492
    util::CheckedLockGuard lock(m_file_system_mutex);
560✔
493
    REALM_ASSERT(m_file_manager);
560✔
494
    return m_file_manager->recovery_directory_path(custom_dir_name);
480✔
495
}
480✔
496

52✔
497
std::vector<std::shared_ptr<SyncSession>> SyncManager::get_all_sessions() const
52✔
498
{
364✔
499
    util::CheckedLockGuard lock(m_session_mutex);
413✔
500
    std::vector<std::shared_ptr<SyncSession>> sessions;
413✔
501
    for (auto& [_, session] : m_sessions) {
705✔
502
        if (auto external_reference = session->existing_external_reference())
707✔
503
            sessions.push_back(std::move(external_reference));
646✔
504
    }
658✔
505
    return sessions;
312✔
506
}
312✔
507

508
std::shared_ptr<SyncSession> SyncManager::get_existing_active_session(const std::string& path) const
509
{
×
510
    util::CheckedLockGuard lock(m_session_mutex);
×
511
    if (auto session = get_existing_session_locked(path)) {
×
512
        if (auto external_reference = session->existing_external_reference())
×
513
            return external_reference;
514
    }
515
    return nullptr;
516
}
517

3,229✔
518
std::shared_ptr<SyncSession> SyncManager::get_existing_session_locked(const std::string& path) const
3,229✔
519
{
22,487✔
520
    auto it = m_sessions.find(path);
22,601✔
521
    return it == m_sessions.end() ? nullptr : it->second;
18,693✔
522
}
19,372✔
523

1,627✔
524
std::shared_ptr<SyncSession> SyncManager::get_existing_session(const std::string& path) const
1,627✔
525
{
11,384✔
526
    util::CheckedLockGuard lock(m_session_mutex);
9,976✔
527
    if (auto session = get_existing_session_locked(path))
10,383✔
528
        return session->external_reference();
2,714✔
529

5,167✔
530
    return nullptr;
8,451✔
531
}
8,451✔
532

1,602✔
533
std::shared_ptr<SyncSession> SyncManager::get_session(std::shared_ptr<DB> db, const RealmConfig& config)
1,602✔
534
{
11,217✔
535
    auto& client = get_sync_client(); // Throws
11,217✔
536
#ifndef __EMSCRIPTEN__
11,217✔
537
    auto path = db->get_path();
9,615✔
538
    REALM_ASSERT_EX(path == config.path, path, config.path);
9,615✔
539
#else
540
    auto path = config.path;
1,602✔
541
#endif
715✔
542
    REALM_ASSERT(config.sync_config);
11,217✔
543

5,895✔
544
    util::CheckedUniqueLock lock(m_session_mutex);
9,623✔
545
    if (auto session = get_existing_session_locked(path)) {
9,623✔
546
        config.sync_config->user->register_session(session);
56✔
547
        return session->external_reference();
759✔
548
    }
1,642✔
549

5,863✔
550
    auto shared_session = SyncSession::create(client, std::move(db), config, this);
10,278✔
551
    m_sessions[path] = shared_session;
10,278✔
552

4,980✔
553
    // Create the external reference immediately to ensure that the session will become
5,863✔
554
    // inactive if an exception is thrown in the following code.
4,980✔
555
    auto external_reference = shared_session->external_reference();
10,278✔
556
    // unlocking m_session_mutex here prevents a deadlock for synchronous network
4,980✔
557
    // transports such as the unit test suite, in the case where the log in request is
4,980✔
558
    // denied by the server: Active -> WaitingForAccessToken -> handle_refresh(401
5,863✔
559
    // error) -> user.log_out() -> unregister_session (locks m_session_mutex again)
5,863✔
560
    lock.unlock();
10,278✔
561
    config.sync_config->user->register_session(std::move(shared_session));
11,161✔
562

5,863✔
563
    return external_reference;
9,567✔
564
}
9,567✔
565

13✔
566
bool SyncManager::has_existing_sessions()
13✔
567
{
91✔
568
    util::CheckedLockGuard lock(m_session_mutex);
91✔
569
    return do_has_existing_sessions();
78✔
570
}
78✔
571

4,502✔
572
bool SyncManager::do_has_existing_sessions()
2,216✔
573
{
27,014✔
574
    return std::any_of(m_sessions.begin(), m_sessions.end(), [](auto& element) {
13,298✔
575
        return element.second->existing_external_reference();
4,514✔
576
    });
12✔
577
}
27,012✔
578

58✔
579
void SyncManager::wait_for_sessions_to_terminate()
58✔
580
{
406✔
581
    auto& client = get_sync_client(); // Throws
406✔
582
    client.wait_for_session_terminations();
348✔
583
}
348✔
584

2,528✔
585
void SyncManager::unregister_session(const std::string& path)
2,528✔
586
{
17,696✔
587
    util::CheckedUniqueLock lock(m_session_mutex);
17,696✔
588
    auto it = m_sessions.find(path);
15,194✔
589
    if (it == m_sessions.end()) {
15,194✔
590
        // The session may already be unregistered. This always happens in the
183✔
591
        // SyncManager destructor, and can also happen due to multiple threads
208✔
592
        // tearing things down at once.
208✔
593
        return;
1,389✔
594
    }
1,389✔
595

7,594✔
596
    // Sync session teardown calls this function, so we need to be careful with
7,594✔
597
    // locking here. We need to unlock `m_session_mutex` before we do anything
7,594✔
598
    // which could result in a re-entrant call or we'll deadlock, which in this
7,594✔
599
    // function means unlocking before we destroy a `shared_ptr<SyncSession>`
7,594✔
600
    // (either the external reference or internal reference versions).
7,594✔
601
    // The external reference version will only be the final reference if
7,594✔
602
    // another thread drops a reference while we're in this function.
7,594✔
603
    // Dropping the final internal reference does not appear to ever actually
7,594✔
604
    // result in a recursive call to this function at the time this comment was
7,594✔
605
    // written, but releasing the lock in that case as well is still safer.
8,986✔
606

6,910✔
607
    if (auto existing_session = it->second->existing_external_reference()) {
15,265✔
608
        // We got here because the session entered the inactive state, but
2,804✔
609
        // there's still someone referencing it so we should leave it be. This
2,804✔
610
        // can happen if the user was logged out, or if all Realms using the
2,804✔
611
        // session were destroyed but the SDK user is holding onto the session.
2,804✔
612

2,804✔
613
        // Explicit unlock so that `existing_session`'s destructor runs after
3,339✔
614
        // the unlock for the reasons noted above
3,339✔
615
        lock.unlock();
6,549✔
616
        return;
6,297✔
617
    }
6,297✔
618

4,790✔
619
    // Remove the session from the map while holding the lock, but then defer
5,647✔
620
    // destroying it until after we unlock the mutex for the reasons noted above.
5,647✔
621
    auto session = m_sessions.extract(it);
10,792✔
622
    lock.unlock();
9,251✔
623
}
9,251✔
624

4✔
625
void SyncManager::set_session_multiplexing(bool allowed)
4✔
626
{
28✔
627
    util::CheckedLockGuard lock(m_mutex);
26✔
628
    if (m_config.multiplex_sessions == allowed)
25✔
629
        return; // Already enabled, we can ignore
14✔
630

6✔
631
    if (m_sync_client)
12✔
632
        throw LogicError(ErrorCodes::IllegalOperation,
1✔
633
                         "Cannot enable session multiplexing after creating the sync client");
2✔
634

8✔
635
    m_config.multiplex_sessions = allowed;
12✔
636
}
12✔
637

7,066✔
638
SyncClient& SyncManager::get_sync_client() const
7,066✔
639
{
49,465✔
640
    util::CheckedLockGuard lock(m_mutex);
46,942✔
641
    if (!m_sync_client)
49,465✔
642
        m_sync_client = create_sync_client(); // Throws
34,324✔
643
    return *m_sync_client;
42,399✔
644
}
42,399✔
645

4,543✔
646
std::unique_ptr<SyncClient> SyncManager::create_sync_client() const
4,543✔
647
{
31,801✔
648
    return std::make_unique<SyncClient>(m_logger_ptr, m_config, weak_from_this());
27,258✔
649
}
27,258✔
650

4,497✔
651
void SyncManager::close_all_sessions()
2,213✔
652
{
29,195✔
653
    // log_out() will call unregister_session(), which requires m_session_mutex,
17,775✔
654
    // so we need to iterate over them without holding the lock.
17,775✔
655
    decltype(m_sessions) sessions;
31,479✔
656
    {
31,479✔
657
        util::CheckedLockGuard lk(m_session_mutex);
31,479✔
658
        m_sessions.swap(sessions);
29,195✔
659
    }
29,214✔
660

13,317✔
661
    for (auto& [_, session] : sessions) {
13,432✔
662
        session->force_close();
2,453✔
663
    }
4,737✔
664

17,775✔
665
    get_sync_client().wait_for_session_terminations();
26,982✔
666
}
26,982✔
667

709✔
668
void SyncManager::set_sync_route(std::string sync_route, bool verified)
709✔
669
{
4,963✔
670
    REALM_ASSERT(!sync_route.empty()); // Cannot be set to empty string
4,963✔
671
    {
4,963✔
672
        util::CheckedLockGuard lock(m_mutex);
4,963✔
673
        m_sync_route = sync_route;
4,963✔
674
        m_sync_route_verified = verified;
4,963✔
675
    }
4,254✔
676
}
4,254✔
677

678
void SyncManager::restart_all_sessions()
679
{
680
    // Restart the sessions that are currently active
×
681
    auto sessions = get_all_sessions();
682
    for (auto& session : sessions) {
×
683
        session->restart_session();
684
    }
685
}
686

6✔
687
void SyncManager::OnlyForTesting::voluntary_disconnect_all_connections(SyncManager& mgr)
6✔
688
{
42✔
689
    mgr.get_sync_client().voluntary_disconnect_all_connections();
36✔
690
}
36✔
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