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

realm / realm-core / 2211

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

push

Evergreen

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

Rework sync user handling and metadata storage

102820 of 195548 branches covered (52.58%)

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

31 existing lines in 8 files now uncovered.

249584 of 269432 relevant lines covered (92.63%)

49986309.51 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
{
42,250✔
44
}
42,250✔
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
{
41,681✔
49
    return std::make_shared<SyncManager>(Private(), std::move(app), sync_route, config, app_id);
41,681✔
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)
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
{
37,224✔
60
    // create the initial logger - if the logger_factory is updated later, a new
22,753✔
61
    // logger will be created at that time.
22,753✔
62
    do_make_logger();
39,401✔
63

22,753✔
64
    if (m_config.metadata_mode == MetadataMode::NoMetadata) {
41,649✔
65
        return;
33,713✔
66
    }
35,961✔
67

7,201✔
68
    bool encrypt = m_config.metadata_mode == MetadataMode::Encryption;
10,113✔
69
    m_metadata_manager = std::make_unique<SyncMetadataManager>(m_file_manager->metadata_path(), encrypt,
7,137✔
70
                                                               m_config.custom_encryption_key);
10,113✔
71

7,201✔
72
    m_metadata_manager->perform_launch_actions(*m_file_manager);
7,137✔
73

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

4,453!
80
bool SyncManager::immediately_run_file_actions(const std::string& realm_path)
2,205✔
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
    }
2,261✔
86
    return false;
2,177✔
87
}
4,425✔
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
    {
40,337✔
94
        util::CheckedLockGuard lock(m_file_system_mutex);
38,089✔
95
        m_metadata_manager = nullptr;
33,529✔
96
    }
38,089✔
97

19,849✔
98
    {
38,089✔
99
        // Destroy all the users.
25,323✔
100
        util::CheckedLockGuard lock(m_user_mutex);
41,251✔
101
        for (auto& user : m_users) {
43,993✔
102
            user->detach_from_sync_manager();
40,831✔
103
        }
43,079✔
104
        m_users.clear();
40,337✔
105
        m_current_user = nullptr;
33,529✔
106
    }
40,337✔
107

26,657✔
108
    {
35,777✔
109
        util::CheckedLockGuard lock(m_mutex);
40,337✔
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);
4,457✔
136
            }
13,435✔
137
        }
4,418✔
138
        REALM_ASSERT_RELEASE(no_sessions);
31,352✔
139

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

26,478✔
148
    {
35,598✔
149
        util::CheckedLockGuard lock(m_mutex);
40,158✔
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)
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

13,763✔
186
void SyncManager::do_make_logger()
13,763✔
187
{
30,187✔
188
    if (m_config.logger_factory) {
30,187✔
189
        m_logger_ptr = m_config.logger_factory(m_config.log_level);
9,458✔
190
    }
9,458✔
191
    else {
39,113✔
192
        m_logger_ptr = util::Logger::get_default_logger();
41,453✔
193
    }
32,147✔
194
}
30,111✔
195

5,869✔
196
const std::shared_ptr<util::Logger>& SyncManager::get_logger() const
1,414✔
197
{
10,105✔
198
    util::CheckedLockGuard lock(m_mutex);
10,105✔
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,125✔
214
void SyncManager::reconnect() const
14,125✔
215
{
7,162✔
216
    util::CheckedLockGuard lock(m_session_mutex);
250✔
217
    for (auto& it : m_sessions) {
105✔
218
        it.second->handle_reconnect();
103✔
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

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;
18,118✔
236
}
18,118✔
237

3,062✔
238
std::shared_ptr<SyncUser> SyncManager::get_user(const std::string& user_id, const std::string& refresh_token,
13,434✔
239
                                                const std::string& access_token, const std::string& device_id)
10,256✔
240
{
41,024✔
241
    std::shared_ptr<SyncUser> user;
42,628✔
242
    {
38,610✔
243
        util::CheckedLockGuard lock(m_user_mutex);
34,734✔
244
        auto it = std::find_if(m_users.begin(), m_users.end(), [&](const auto& user) {
21,300✔
245
            return user->identity() == user_id && user->state() != SyncUser::State::Removed;
17,954✔
246
        });
13,426✔
247
        if (it == m_users.end()) {
42,220✔
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,862✔
251
            m_users.emplace(m_users.begin(), new_user);
41,862✔
252
            {
36,750✔
253
                util::CheckedLockGuard lock(m_file_system_mutex);
41,862✔
254
                // m_current_user is normally set very indirectly via the metadata manger
24,286✔
255
                if (!m_metadata_manager)
40,288✔
256
                    m_current_user = new_user;
33,568✔
257
            }
40,288✔
258
            return new_user;
31,878✔
259
        }
31,005✔
260

2,022✔
261
        // LoggedOut => LoggedIn
2,022✔
262
        user = *it;
744✔
263
        REALM_ASSERT(user->state() != SyncUser::State::Removed);
744✔
264
    }
1,433✔
265
    user->log_in(access_token, refresh_token);
2,302✔
266
    return user;
2,118✔
267
}
1,249✔
268

1,193✔
269
std::vector<std::shared_ptr<SyncUser>> SyncManager::all_users()
1,193✔
270
{
3,550✔
271
    util::CheckedLockGuard lock(m_user_mutex);
3,536✔
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,461✔
275
                                     if (should_remove) {
1,461✔
276
                                         user->detach_from_sync_manager();
613✔
277
                                     }
613✔
278
                                     return should_remove;
1,900✔
279
                                 }),
1,900✔
280
                  m_users.end());
6,422✔
281
    return m_users;
3,672✔
282
}
1,490✔
283

2,520✔
284
std::shared_ptr<SyncUser> SyncManager::get_user_for_identity(std::string const& identity) const noexcept
6,976✔
285
{
10,092✔
286
    auto is_active_user = [identity](auto& el) {
10,152✔
287
        return el->identity() == identity;
10,190✔
288
    };
10,166✔
289
    auto it = std::find_if(m_users.begin(), m_users.end(), is_active_user);
10,130✔
290
    return it == m_users.end() ? nullptr : *it;
7,540✔
291
}
7,554✔
292

9,356✔
293
std::shared_ptr<SyncUser> SyncManager::get_current_user() const
11,851✔
294
{
35,215✔
295
    util::CheckedLockGuard lock(m_user_mutex);
39,919✔
296

25,683✔
297
    if (m_current_user)
28,218✔
298
        return m_current_user;
27,846✔
299
    util::CheckedLockGuard fs_lock(m_file_system_mutex);
438✔
300
    if (!m_metadata_manager)
464✔
301
        return nullptr;
254✔
302

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

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

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

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

870✔
320
    util::CheckedLockGuard fs_lock(m_file_system_mutex);
1,074✔
321
    bool was_active = m_current_user.get() == &user ||
1,158✔
322
                      (m_metadata_manager && m_metadata_manager->get_current_user_identity() == user.identity());
1,255✔
323
    if (!was_active)
1,593✔
324
        return;
1,041✔
325

1,130✔
326
    // Set the current active user to the next logged in user, or null if none
978✔
327
    if (active_user != user_pos) {
1,254✔
328
        m_current_user = *active_user;
1,626✔
329
        if (m_metadata_manager)
1,642✔
330
            m_metadata_manager->set_current_user_identity((*active_user)->identity());
1,714✔
331
    }
248✔
332
    else {
608✔
333
        m_current_user = nullptr;
524✔
334
        if (m_metadata_manager)
14,709✔
335
            m_metadata_manager->set_current_user_identity("");
14,489✔
336
    }
14,557✔
337
}
7,570✔
338

16,473✔
339
void SyncManager::set_current_user(const std::string& user_id)
16,473✔
340
{
12,900✔
341
    util::CheckedLockGuard lock(m_user_mutex);
9,506✔
342

5,882✔
343
    m_current_user = get_user_for_identity(user_id);
18,751✔
344
    util::CheckedLockGuard fs_lock(m_file_system_mutex);
14,178✔
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)
4,888✔
350
{
256✔
351
    util::CheckedLockGuard lock(m_user_mutex);
231✔
352
    if (auto user = get_user_for_identity(user_id))
231✔
353
        user->invalidate();
232✔
354
}
14,251✔
355

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

30✔
365
    if (!user)
71✔
366
        return;
12✔
367

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

6,844✔
373
    if (m_current_user && m_current_user->identity() == user->identity())
4,497✔
374
        m_current_user = nullptr;
6,874✔
375

6,844✔
376
    util::CheckedLockGuard fs_lock(m_file_system_mutex);
60✔
377
    if (!m_metadata_manager)
70✔
378
        return;
4,447✔
379

4,481✔
380
    auto users = m_metadata_manager->all_unmarked_users();
4,511✔
381
    for (size_t i = 0; i < users.size(); i++) {
4,523✔
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());
4,505✔
385
            metadata.remove();
2,257✔
386
            break;
2,261✔
387
        }
4,505✔
388
    }
4,497✔
389
}
4,485✔
390

13,731✔
391
SyncManager::~SyncManager() NO_THREAD_SAFETY_ANALYSIS
9,007✔
392
{
34,677✔
393
    // Grab the current sessions under a lock so we can shut them down. We have to
20,530✔
394
    // release the lock before calling them as shutdown_and_wait() will call
23,104✔
395
    // back into us.
23,104✔
396
    decltype(m_sessions) current_sessions;
39,401✔
397
    {
41,649✔
398
        util::CheckedLockGuard lk(m_session_mutex);
41,649✔
399
        m_sessions.swap(current_sessions);
32,500✔
400
    }
32,514✔
401

22,467✔
402
    for (auto& [_, session] : current_sessions) {
22,505✔
403
        session->detach_from_sync_manager();
13,352✔
404
    }
18,076✔
405

31,746✔
406
    {
41,292✔
407
        util::CheckedLockGuard lk(m_user_mutex);
36,808✔
408
        for (auto& user : m_users) {
22,930✔
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

18✔
421
std::shared_ptr<SyncUser> SyncManager::get_existing_logged_in_user(const std::string& user_id) const
18✔
422
{
54✔
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