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

realm / realm-core / 2210

09 Apr 2024 03:41PM UTC coverage: 92.601% (+0.5%) from 92.106%
2210

push

Evergreen

web-flow
Merge pull request #7300 from realm/tg/rework-metadata-storage

Rework sync user handling and metadata storage

102800 of 195548 branches covered (52.57%)

3051 of 3153 new or added lines in 46 files covered. (96.76%)

41 existing lines in 11 files now uncovered.

249129 of 269035 relevant lines covered (92.6%)

46864217.27 hits per line

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

98.81
/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
{
39,749✔
44
}
39,749✔
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)
2,264✔
48
{
39,488✔
49
    return std::make_shared<SyncManager>(Private(), std::move(app), sync_route, config, app_id);
39,488✔
50
}
37,224✔
51

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

20,576✔
64
    if (m_config.metadata_mode == MetadataMode::NoMetadata) {
39,472✔
65
        return;
31,536✔
66
    }
33,784✔
67

5,024✔
68
    bool encrypt = m_config.metadata_mode == MetadataMode::Encryption;
7,936✔
69
    m_metadata_manager = std::make_unique<SyncMetadataManager>(m_file_manager->metadata_path(), encrypt,
4,960✔
70
                                                               m_config.custom_encryption_key);
7,936✔
71

5,024✔
72
    m_metadata_manager->perform_launch_actions(*m_file_manager);
4,960✔
73

5,066✔
74
    // Load persisted users into the users map.
2,166✔
75
    for (auto user : m_metadata_manager->all_logged_in_users()) {
2,292✔
76
        m_users.push_back(std::make_shared<SyncUser>(SyncUser::Private(), user, this));
1,674✔
77
    }
252✔
78
}
4,266✔
79

2,276!
80
bool SyncManager::immediately_run_file_actions(const std::string& realm_path)
28✔
81
{
112✔
82
    util::CheckedLockGuard lock(m_file_system_mutex);
112✔
83
    if (m_metadata_manager) {
112✔
84
        return m_metadata_manager->perform_file_actions(*m_file_manager, realm_path);
84✔
85
    }
84✔
86
    return false;
87
}
2,248✔
88

8,978✔
89
void SyncManager::tear_down_for_testing()
8,978!
90
{
31,352✔
91
    close_all_sessions();
35,912✔
92

22,232✔
93
    {
38,160✔
94
        util::CheckedLockGuard lock(m_file_system_mutex);
35,912✔
95
        m_metadata_manager = nullptr;
31,352✔
96
    }
35,912✔
97

17,672✔
98
    {
35,912✔
99
        // Destroy all the users.
23,146✔
100
        util::CheckedLockGuard lock(m_user_mutex);
39,074✔
101
        for (auto& user : m_users) {
41,816✔
102
            user->detach_from_sync_manager();
38,654✔
103
        }
40,902✔
104
        m_users.clear();
38,160✔
105
        m_current_user = nullptr;
31,352✔
106
    }
38,160✔
107

24,480✔
108
    {
33,600✔
109
        util::CheckedLockGuard lock(m_mutex);
38,160✔
110
        // Stop the client. This will abort any uploads that inactive sessions are waiting for.
22,232✔
111
        if (m_sync_client)
35,912✔
112
            m_sync_client->stop();
31,352✔
113
    }
35,912✔
114

22,232✔
115
    {
31,352✔
116
        util::CheckedUniqueLock lock(m_session_mutex);
35,912!
117

17,672✔
118
        bool no_sessions = !do_has_existing_sessions();
31,352✔
119
        // There's a race between this function and sessions tearing themselves down waiting for m_session_mutex.
17,672✔
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;
8,978!
123
        };
4,418✔
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();
4,418✔
129
        }
4,418✔
130
        // Callers of `SyncManager::tear_down_for_testing` should ensure there are no existing sessions
22,232✔
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);
2,264✔
136
            }
11,242✔
137
        }
4,418✔
138
        REALM_ASSERT_RELEASE(no_sessions);
31,352✔
139

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

24,372✔
148
    {
33,492✔
149
        util::CheckedLockGuard lock(m_mutex);
38,052✔
150
        // Destroy the client now that we have no remaining sessions.
22,232✔
151
        m_sync_client = nullptr;
35,912✔
152
        m_logger_ptr.reset();
31,352✔
153
    }
35,912✔
154

22,232✔
155
    {
35,912✔
156
        util::CheckedLockGuard lock(m_file_system_mutex);
35,912✔
157
        if (m_file_manager)
35,912✔
158
            util::try_remove_dir_recursive(m_file_manager->base_path());
35,912✔
159
        m_file_manager = nullptr;
35,912✔
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)
2,264!
179
        throw LogicError(ErrorCodes::IllegalOperation,
180
                         "Cannot set the logger factory after creating the sync client");
181

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

11,570✔
186
void SyncManager::do_make_logger()
11,570✔
187
{
27,994✔
188
    if (m_config.logger_factory) {
27,994✔
189
        m_logger_ptr = m_config.logger_factory(m_config.log_level);
9,458✔
190
    }
9,458✔
191
    else {
36,920✔
192
        m_logger_ptr = util::Logger::get_default_logger();
39,260✔
193
    }
29,954✔
194
}
27,918✔
195

3,676✔
196
const std::shared_ptr<util::Logger>& SyncManager::get_logger() const
1,413✔
197
{
7,912✔
198
    util::CheckedLockGuard lock(m_mutex);
7,912✔
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
}
7,093✔
213

7,097✔
214
void SyncManager::reconnect() const
7,097✔
215
{
134✔
216
    util::CheckedLockGuard lock(m_session_mutex);
132✔
217
    for (auto& it : m_sessions) {
56✔
218
        it.second->handle_reconnect();
54✔
219
    }
50✔
220
}
132✔
221

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

13,352!
228
bool SyncManager::perform_metadata_update(util::FunctionRef<void(SyncMetadataManager&)> update_function) const
13,352!
229
{
53,408✔
230
    util::CheckedLockGuard lock(m_file_system_mutex);
48,428✔
231
    if (!m_metadata_manager) {
48,428✔
232
        return false;
30,096✔
233
    }
30,096✔
234
    update_function(*m_metadata_manager);
19,920✔
235
    return true;
16,692✔
236
}
16,692✔
237

1,637✔
238
std::shared_ptr<SyncUser> SyncManager::get_user(const std::string& user_id, const std::string& refresh_token,
12,008✔
239
                                                const std::string& access_token, const std::string& device_id)
10,256✔
240
{
41,024✔
241
    std::shared_ptr<SyncUser> user;
41,903✔
242
    {
37,885✔
243
        util::CheckedLockGuard lock(m_user_mutex);
34,009✔
244
        auto it = std::find_if(m_users.begin(), m_users.end(), [&](const auto& user) {
21,187✔
245
            return user->identity() == user_id && user->state() != SyncUser::State::Removed;
17,342✔
246
        });
12,814✔
247
        if (it == m_users.end()) {
41,608✔
248
            // No existing user.
24,952✔
249
            auto new_user = std::make_shared<SyncUser>(SyncUser::Private(), refresh_token, user_id, access_token,
40,288✔
250
                                                       device_id, this);
41,161✔
251
            m_users.emplace(m_users.begin(), new_user);
41,161✔
252
            {
36,049✔
253
                util::CheckedLockGuard lock(m_file_system_mutex);
41,161✔
254
                // m_current_user is normally set very indirectly via the metadata manger
23,585✔
255
                if (!m_metadata_manager)
40,288✔
256
                    m_current_user = new_user;
33,568✔
257
            }
40,288✔
258
            return new_user;
31,177✔
259
        }
30,304✔
260

1,321✔
261
        // LoggedOut => LoggedIn
1,321✔
262
        user = *it;
740✔
263
        REALM_ASSERT(user->state() != SyncUser::State::Removed);
740✔
264
    }
736✔
265
    user->log_in(access_token, refresh_token);
1,605✔
266
    return user;
1,421✔
267
}
552✔
268

496✔
269
std::vector<std::shared_ptr<SyncUser>> SyncManager::all_users()
496✔
270
{
2,853✔
271
    util::CheckedLockGuard lock(m_user_mutex);
2,839✔
272
    m_users.erase(std::remove_if(m_users.begin(), m_users.end(),
1,956✔
273
                                 [](auto& user) {
1,914✔
274
                                     bool should_remove = (user->state() == SyncUser::State::Removed);
1,455✔
275
                                     if (should_remove) {
1,455✔
276
                                         user->detach_from_sync_manager();
607✔
277
                                     }
607✔
278
                                     return should_remove;
1,900✔
279
                                 }),
1,900✔
280
                  m_users.end());
4,239✔
281
    return m_users;
1,489✔
282
}
1,489✔
283

2,519✔
284
std::shared_ptr<SyncUser> SyncManager::get_user_for_identity(std::string const& identity) const noexcept
4,793✔
285
{
10,092✔
286
    auto is_active_user = [identity](auto& el) {
10,152✔
287
        return el->identity() == identity;
10,186✔
288
    };
10,162✔
289
    auto it = std::find_if(m_users.begin(), m_users.end(), is_active_user);
10,126✔
290
    return it == m_users.end() ? nullptr : *it;
7,536✔
291
}
7,554✔
292

9,356✔
293
std::shared_ptr<SyncUser> SyncManager::get_current_user() const
10,757✔
294
{
34,121✔
295
    util::CheckedLockGuard lock(m_user_mutex);
38,825✔
296

24,589✔
297
    if (m_current_user)
28,192✔
298
        return m_current_user;
27,820✔
299
    util::CheckedLockGuard fs_lock(m_file_system_mutex);
412✔
300
    if (!m_metadata_manager)
440✔
301
        return nullptr;
230✔
302

210✔
303
    auto cur_user_ident = m_metadata_manager->get_current_user_identity();
336✔
304
    return cur_user_ident ? get_user_for_identity(*cur_user_ident) : nullptr;
252✔
305
}
252✔
306

190✔
307
void SyncManager::log_out_user(const SyncUser& user)
190✔
308
{
664✔
309
    util::CheckedLockGuard lock(m_user_mutex);
664✔
310

534✔
311
    // Move this user to the end of the vector
534✔
312
    auto user_pos = std::partition(m_users.begin(), m_users.end(), [&](auto& u) {
1,008✔
313
        return u.get() != &user;
850✔
314
    });
2,255✔
315

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

472✔
320
    util::CheckedLockGuard fs_lock(m_file_system_mutex);
676✔
321
    bool was_active = m_current_user.get() == &user ||
760✔
322
                      (m_metadata_manager && m_metadata_manager->get_current_user_identity() == user.identity());
856✔
323
    if (!was_active)
1,194✔
324
        return;
642✔
325

460✔
326
    // Set the current active user to the next logged in user, or null if none
308✔
327
    if (active_user != user_pos) {
584✔
328
        m_current_user = *active_user;
955✔
329
        if (m_metadata_manager)
971✔
330
            m_metadata_manager->set_current_user_identity((*active_user)->identity());
1,043✔
331
    }
248✔
332
    else {
608✔
333
        m_current_user = nullptr;
524✔
334
        if (m_metadata_manager)
7,691✔
335
            m_metadata_manager->set_current_user_identity("");
7,471✔
336
    }
7,539✔
337
}
552✔
338

9,455✔
339
void SyncManager::set_current_user(const std::string& user_id)
9,455✔
340
{
8,289✔
341
    util::CheckedLockGuard lock(m_user_mutex);
9,497✔
342

5,873✔
343
    m_current_user = get_user_for_identity(user_id);
14,140✔
344
    util::CheckedLockGuard fs_lock(m_file_system_mutex);
11,771✔
345
    if (m_metadata_manager)
9,488✔
346
        m_metadata_manager->set_current_user_identity(user_id);
6,672✔
347
}
7,116✔
348

50✔
349
void SyncManager::remove_user(const std::string& user_id)
2,481✔
350
{
216✔
351
    util::CheckedLockGuard lock(m_user_mutex);
216✔
352
    if (auto user = get_user_for_identity(user_id))
216✔
353
        user->invalidate();
192✔
354
}
7,233✔
355

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

30✔
365
    if (!user)
70✔
366
        return;
11✔
367

41✔
368
    // Deletion should happen immediately, not when we do the cleanup
50✔
369
    // task on next launch.
50✔
370
    m_users.erase(it);
3,649✔
371
    user->detach_from_sync_manager();
3,659✔
372

3,629✔
373
    if (m_current_user && m_current_user->identity() == user->identity())
2,319✔
374
        m_current_user = nullptr;
3,659✔
375

3,629✔
376
    util::CheckedLockGuard fs_lock(m_file_system_mutex);
60✔
377
    if (!m_metadata_manager)
70✔
378
        return;
2,269✔
379

2,303✔
380
    auto users = m_metadata_manager->all_unmarked_users();
2,333✔
381
    for (size_t i = 0; i < users.size(); i++) {
2,345✔
382
        auto metadata = users.get(i);
92✔
383
        if (user->identity() == metadata.identity()) {
92✔
384
            m_file_manager->remove_user_realms(metadata.identity(), metadata.realm_file_paths());
2,328✔
385
            metadata.remove();
80✔
386
            break;
84✔
387
        }
2,328✔
388
    }
2,320✔
389
}
2,308✔
390

11,554✔
391
SyncManager::~SyncManager() NO_THREAD_SAFETY_ANALYSIS
6,830✔
392
{
32,500✔
393
    // Grab the current sessions under a lock so we can shut them down. We have to
18,354✔
394
    // release the lock before calling them as shutdown_and_wait() will call
23,078✔
395
    // back into us.
23,078✔
396
    decltype(m_sessions) current_sessions;
37,224✔
397
    {
39,472✔
398
        util::CheckedLockGuard lk(m_session_mutex);
39,472✔
399
        m_sessions.swap(current_sessions);
32,500✔
400
    }
32,514✔
401

18,164✔
402
    for (auto& [_, session] : current_sessions) {
18,202✔
403
        session->detach_from_sync_manager();
9,049✔
404
    }
13,773✔
405

27,443✔
406
    {
36,989✔
407
        util::CheckedLockGuard lk(m_user_mutex);
32,505✔
408
        for (auto& user : m_users) {
18,627✔
409
            user->detach_from_sync_manager();
9,894✔
410
        }
5,170✔
411
    }
37,224✔
412

23,052✔
413
    {
32,500✔
414
        util::CheckedLockGuard lk(m_mutex);
37,224✔
415
        // Stop the client. This will abort any uploads that inactive sessions are waiting for.
13,854✔
416
        if (m_sync_client)
37,224✔
417
            m_sync_client->stop();
9,630✔
418
    }
27,918✔
419
}
27,918✔
420

15✔
421
std::shared_ptr<SyncUser> SyncManager::get_existing_logged_in_user(const std::string& user_id) const
15✔
422
{
51✔
423
    util::CheckedLockGuard lock(m_user_mutex);
48✔
424
    auto user = get_user_for_identity(user_id);
48✔
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

108✔
435
static std::string string_from_partition(const std::string& partition)
108✔
436
{
432✔
437
    bson::Bson partition_value = bson::parse(partition);
328✔
438
    switch (partition_value.type()) {
328✔
439
        case bson::Bson::Type::Int32:
16✔
440
            return util::format("i_%1", static_cast<int32_t>(partition_value));
16✔
441
        case bson::Bson::Type::Int64:
100✔
442
            return util::format("l_%1", static_cast<int64_t>(partition_value));
100✔
443
        case bson::Bson::Type::String:
268✔
444
            return util::format("s_%1", static_cast<std::string>(partition_value));
268✔
445
        case bson::Bson::Type::ObjectId:
16✔
446
            return util::format("o_%1", static_cast<ObjectId>(partition_value).to_string());
16✔
447
        case bson::Bson::Type::Uuid:
16✔
448
            return util::format("u_%1", static_cast<UUID>(partition_value).to_string());
16✔
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.",
108✔
454
                                                        partition_value.to_string()));
108✔
455
    }
324✔
456
}
324✔
457

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

288✔
467
        // Attempt to make a nicer filename which will ease debugging when
360✔
468
        // locating files in the filesystem.
360✔
469
        auto file_name = [&]() -> std::string {
464✔
470
            if (custom_file_name) {
464✔
471
                return *custom_file_name;
208✔
472
            }
100✔
473
            if (config.flx_sync_requested) {
340✔
474
                REALM_ASSERT_DEBUG(config.partition_value.empty());
16✔
475
                return "flx_sync_default";
120✔
476
            }
120✔
477
            return string_from_partition(config.partition_value);
468✔
478
        }();
468✔
479
        path = m_file_manager->realm_file_path(user->identity(), user->legacy_identities(), file_name,
576✔
480
                                               config.partition_value);
504✔
481
    }
548✔
482
    // Report the use of a Realm for this user, so the metadata can track it for clean up.
304✔
483
    perform_metadata_update([&](const auto& manager) {
436✔
484
        auto metadata = manager.get_or_make_user_metadata(user->identity());
352✔
485
        metadata->add_realm_file_path(path);
408✔
486
    });
408✔
487
    return path;
432✔
488
}
432✔
489

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

104✔
497
std::vector<std::shared_ptr<SyncSession>> SyncManager::get_all_sessions() const
104✔
498
{
416✔
499
    util::CheckedLockGuard lock(m_session_mutex);
514✔
500
    std::vector<std::shared_ptr<SyncSession>> sessions;
514✔
501
    for (auto& [_, session] : m_sessions) {
804✔
502
        if (auto external_reference = session->existing_external_reference())
808✔
503
            sessions.push_back(std::move(external_reference));
698✔
504
    }
710✔
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

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

3,254✔
524
std::shared_ptr<SyncSession> SyncManager::get_existing_session(const std::string& path) const
3,254✔
525
{
13,011✔
526
    util::CheckedLockGuard lock(m_session_mutex);
10,195✔
527
    if (auto session = get_existing_session_locked(path))
11,009✔
528
        return session->external_reference();
4,122✔
529

6,575✔
530
    return nullptr;
8,451✔
531
}
8,451✔
532

3,204✔
533
std::shared_ptr<SyncSession> SyncManager::get_session(std::shared_ptr<DB> db, const RealmConfig& config)
3,204✔
534
{
12,819✔
535
    auto& client = get_sync_client(); // Throws
12,819✔
536
#ifndef __EMSCRIPTEN__
12,819✔
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;
3,204✔
541
#endif
1,430✔
542
    REALM_ASSERT(config.sync_config);
12,819✔
543

7,497✔
544
    util::CheckedUniqueLock lock(m_session_mutex);
9,631✔
545
    if (auto session = get_existing_session_locked(path)) {
9,631✔
546
        config.sync_config->user->register_session(session);
64✔
547
        return session->external_reference();
1,470✔
548
    }
3,236✔
549

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

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

7,457✔
563
    return external_reference;
9,567✔
564
}
9,567✔
565

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

9,004✔
572
bool SyncManager::do_has_existing_sessions()
4,432✔
573
{
27,016✔
574
    return std::any_of(m_sessions.begin(), m_sessions.end(), [](auto& element) {
13,300✔
575
        return element.second->existing_external_reference();
9,016✔
576
    });
12✔
577
}
27,012✔
578

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

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

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

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

3,205✔
613
        // Explicit unlock so that `existing_session`'s destructor runs after
4,275✔
614
        // the unlock for the reasons noted above
4,275✔
615
        lock.unlock();
7,485✔
616
        return;
6,983✔
617
    }
6,983✔
618

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

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

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

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

14,132✔
638
SyncClient& SyncManager::get_sync_client() const
14,132✔
639
{
56,531✔
640
    util::CheckedLockGuard lock(m_mutex);
51,485✔
641
    if (!m_sync_client)
56,531✔
642
        m_sync_client = create_sync_client(); // Throws
41,390✔
643
    return *m_sync_client;
42,399✔
644
}
42,399✔
645

9,086✔
646
std::unique_ptr<SyncClient> SyncManager::create_sync_client() const
9,086✔
647
{
36,344✔
648
    return std::make_unique<SyncClient>(m_logger_ptr, m_config, weak_from_this());
27,258✔
649
}
27,258✔
650

8,994✔
651
void SyncManager::close_all_sessions()
4,426✔
652
{
31,408✔
653
    // log_out() will call unregister_session(), which requires m_session_mutex,
22,272✔
654
    // so we need to iterate over them without holding the lock.
22,272✔
655
    decltype(m_sessions) sessions;
35,976✔
656
    {
35,976✔
657
        util::CheckedLockGuard lk(m_session_mutex);
35,976✔
658
        m_sessions.swap(sessions);
31,408✔
659
    }
31,445✔
660

13,354✔
661
    for (auto& [_, session] : sessions) {
13,469✔
662
        session->force_close();
4,666✔
663
    }
9,234✔
664

22,272✔
665
    get_sync_client().wait_for_session_terminations();
26,982✔
666
}
26,982✔
667

1,418✔
668
void SyncManager::set_sync_route(std::string sync_route, bool verified)
1,418✔
669
{
5,672✔
670
    REALM_ASSERT(!sync_route.empty()); // Cannot be set to empty string
5,672✔
671
    {
5,672✔
672
        util::CheckedLockGuard lock(m_mutex);
5,672✔
673
        m_sync_route = sync_route;
5,672✔
674
        m_sync_route_verified = verified;
5,672✔
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

12✔
687
void SyncManager::OnlyForTesting::voluntary_disconnect_all_connections(SyncManager& mgr)
12✔
688
{
48✔
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