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

realm / realm-core / 2214

10 Apr 2024 11:21PM UTC coverage: 91.813% (-0.8%) from 92.623%
2214

push

Evergreen

web-flow
Add missing availability checks for SecCopyErrorMessageString (#7577)

This requires iOS 11.3 and we currently target iOS 11.

94848 of 175770 branches covered (53.96%)

7 of 22 new or added lines in 2 files covered. (31.82%)

1815 existing lines in 77 files now uncovered.

242945 of 264608 relevant lines covered (91.81%)

6136478.37 hits per line

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

78.06
/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/app_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
{
5,092✔
44
}
5,092✔
45

46
std::shared_ptr<SyncManager> SyncManager::create(const SyncClientConfig& config)
47
{
4,457✔
48
    return std::make_shared<SyncManager>(Private(), config);
4,457✔
49
}
4,457✔
50

51
SyncManager::SyncManager(Private, const SyncClientConfig& config)
52
    : m_config(config)
53
{
4,457✔
54
    // create the initial logger - if the logger_factory is updated later, a new
2,193✔
55
    // logger will be created at that time.
2,193✔
56
    do_make_logger();
4,457✔
57
}
4,457✔
58

59
void SyncManager::tear_down_for_testing()
60
{
4,425✔
61
    close_all_sessions();
4,425✔
62

2,177✔
63
    {
4,425✔
64
        util::CheckedLockGuard lock(m_mutex);
4,425✔
65
        // Stop the client. This will abort any uploads that inactive sessions are waiting for.
2,177✔
66
        if (m_sync_client)
4,425✔
67
            m_sync_client->stop();
4,425✔
68
    }
4,425✔
69

2,177✔
70
    {
4,425✔
71
        util::CheckedUniqueLock lock(m_session_mutex);
4,425✔
72

2,177✔
73
        bool no_sessions = !do_has_existing_sessions();
4,425✔
74
        // There's a race between this function and sessions tearing themselves down waiting for m_session_mutex.
2,177✔
75
        // So we give up to a 5 second grace period for any sessions being torn down to unregister themselves.
2,177✔
76
        auto since_poll_start = [start = std::chrono::steady_clock::now()] {
2,177✔
UNCOV
77
            return std::chrono::steady_clock::now() - start;
×
UNCOV
78
        };
×
79
        for (; !no_sessions && since_poll_start() < std::chrono::seconds(5);
4,425!
80
             no_sessions = !do_has_existing_sessions()) {
2,177✔
UNCOV
81
            lock.unlock();
×
UNCOV
82
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
×
UNCOV
83
            lock.lock();
×
UNCOV
84
        }
×
85
        // Callers of `SyncManager::tear_down_for_testing` should ensure there are no existing sessions
2,177✔
86
        // prior to calling `tear_down_for_testing`.
2,177✔
87
        if (!no_sessions) {
4,425✔
UNCOV
88
            util::CheckedLockGuard lock(m_mutex);
×
UNCOV
89
            for (auto session : m_sessions) {
×
UNCOV
90
                m_logger_ptr->error("open session at path '%1'", session.first);
×
UNCOV
91
            }
×
UNCOV
92
        }
×
93
        REALM_ASSERT_RELEASE(no_sessions);
4,425✔
94

2,177✔
95
        // Destroy any inactive sessions.
2,177✔
96
        // FIXME: We shouldn't have any inactive sessions at this point! Sessions are expected to
2,177✔
97
        // remain inactive until their final upload completes, at which point they are unregistered
2,177✔
98
        // and destroyed. Our call to `sync::Client::stop` above aborts all uploads, so all sessions
2,177✔
99
        // should have already been destroyed.
2,177✔
100
        m_sessions.clear();
4,425✔
101
    }
4,425✔
102

2,177✔
103
    {
4,425✔
104
        util::CheckedLockGuard lock(m_mutex);
4,425✔
105
        // Destroy the client now that we have no remaining sessions.
2,177✔
106
        m_sync_client.reset();
4,425✔
107
        m_logger_ptr.reset();
4,425✔
108
    }
4,425✔
109
}
4,425✔
110

111
void SyncManager::set_log_level(util::Logger::Level level) noexcept
UNCOV
112
{
×
UNCOV
113
    util::CheckedLockGuard lock(m_mutex);
×
UNCOV
114
    m_config.log_level = level;
×
115
    // Update the level threshold in the already created logger
UNCOV
116
    if (m_logger_ptr) {
×
UNCOV
117
        m_logger_ptr->set_level_threshold(level);
×
UNCOV
118
    }
×
UNCOV
119
}
×
120

121
void SyncManager::set_logger_factory(SyncClientConfig::LoggerFactory factory)
UNCOV
122
{
×
UNCOV
123
    util::CheckedLockGuard lock(m_mutex);
×
UNCOV
124
    m_config.logger_factory = std::move(factory);
×
125

126
    if (m_sync_client)
×
127
        throw LogicError(ErrorCodes::IllegalOperation,
×
UNCOV
128
                         "Cannot set the logger factory after creating the sync client");
×
129

130
    // Create a new logger using the new factory
UNCOV
131
    do_make_logger();
×
UNCOV
132
}
×
133

134
void SyncManager::do_make_logger()
135
{
4,457✔
136
    if (m_config.logger_factory) {
4,457✔
UNCOV
137
        m_logger_ptr = m_config.logger_factory(m_config.log_level);
×
UNCOV
138
    }
×
139
    else {
4,457✔
140
        m_logger_ptr = util::Logger::get_default_logger();
4,457✔
141
    }
4,457✔
142
    REALM_ASSERT(m_logger_ptr);
4,457✔
143
}
4,457✔
144

145
const std::shared_ptr<util::Logger>& SyncManager::get_logger() const
146
{
4,246✔
147
    util::CheckedLockGuard lock(m_mutex);
4,246✔
148
    return m_logger_ptr;
4,246✔
149
}
4,246✔
150

151
void SyncManager::set_user_agent(std::string user_agent)
UNCOV
152
{
×
UNCOV
153
    util::CheckedLockGuard lock(m_mutex);
×
UNCOV
154
    m_config.user_agent_application_info = std::move(user_agent);
×
UNCOV
155
}
×
156

157
void SyncManager::set_timeouts(SyncClientTimeouts timeouts)
UNCOV
158
{
×
UNCOV
159
    util::CheckedLockGuard lock(m_mutex);
×
UNCOV
160
    m_config.timeouts = timeouts;
×
UNCOV
161
}
×
162

163
void SyncManager::reconnect() const
164
{
×
UNCOV
165
    util::CheckedLockGuard lock(m_session_mutex);
×
166
    for (auto& it : m_sessions) {
×
UNCOV
167
        it.second->handle_reconnect();
×
168
    }
×
169
}
×
170

171
util::Logger::Level SyncManager::log_level() const noexcept
UNCOV
172
{
×
UNCOV
173
    util::CheckedLockGuard lock(m_mutex);
×
174
    return m_config.log_level;
×
UNCOV
175
}
×
176

177
SyncManager::~SyncManager() NO_THREAD_SAFETY_ANALYSIS
178
{
4,457✔
179
    // Grab the current sessions under a lock so we can shut them down. We have to
2,193✔
180
    // release the lock before calling them as shutdown_and_wait() will call
2,193✔
181
    // back into us.
2,193✔
182
    decltype(m_sessions) current_sessions;
4,457✔
183
    {
4,457✔
184
        util::CheckedLockGuard lk(m_session_mutex);
4,457✔
185
        m_sessions.swap(current_sessions);
4,457✔
186
    }
4,457✔
187

2,193✔
188
    for (auto& [_, session] : current_sessions) {
2,193✔
UNCOV
189
        session->detach_from_sync_manager();
×
UNCOV
190
    }
×
191

2,193✔
192
    {
4,457✔
193
        util::CheckedLockGuard lk(m_mutex);
4,457✔
194
        // Stop the client. This will abort any uploads that inactive sessions are waiting for.
2,193✔
195
        if (m_sync_client)
4,457✔
196
            m_sync_client->stop();
2✔
197
    }
4,457✔
198
}
4,457✔
199

200
std::vector<std::shared_ptr<SyncSession>> SyncManager::get_all_sessions() const
201
{
52✔
202
    util::CheckedLockGuard lock(m_session_mutex);
52✔
203
    std::vector<std::shared_ptr<SyncSession>> sessions;
52✔
204
    for (auto& [_, session] : m_sessions) {
101✔
205
        if (auto external_reference = session->existing_external_reference())
101✔
206
            sessions.push_back(std::move(external_reference));
99✔
207
    }
101✔
208
    return sessions;
52✔
209
}
52✔
210

211
std::vector<std::shared_ptr<SyncSession>> SyncManager::get_all_sessions_for(const SyncUser& user) const
212
{
14,121✔
213
    util::CheckedLockGuard lock(m_session_mutex);
14,121✔
214
    std::vector<std::shared_ptr<SyncSession>> sessions;
14,121✔
215
    for (auto& [_, session] : m_sessions) {
7,148✔
216
        if (session->user().get() == &user) {
238✔
217
            if (auto external_reference = session->existing_external_reference())
99✔
218
                sessions.push_back(std::move(external_reference));
99✔
219
        }
99✔
220
    }
238✔
221
    return sessions;
14,121✔
222
}
14,121✔
223

224
std::shared_ptr<SyncSession> SyncManager::get_existing_active_session(const std::string& path) const
UNCOV
225
{
×
UNCOV
226
    util::CheckedLockGuard lock(m_session_mutex);
×
UNCOV
227
    if (auto session = get_existing_session_locked(path)) {
×
UNCOV
228
        if (auto external_reference = session->existing_external_reference())
×
UNCOV
229
            return external_reference;
×
UNCOV
230
    }
×
UNCOV
231
    return nullptr;
×
UNCOV
232
}
×
233

234
std::shared_ptr<SyncSession> SyncManager::get_existing_session_locked(const std::string& path) const
235
{
3,179✔
236
    auto it = m_sessions.find(path);
3,179✔
237
    return it == m_sessions.end() ? nullptr : it->second;
3,062✔
238
}
3,179✔
239

240
std::shared_ptr<SyncSession> SyncManager::get_existing_session(const std::string& path) const
241
{
1,605✔
242
    util::CheckedLockGuard lock(m_session_mutex);
1,605✔
243
    if (auto session = get_existing_session_locked(path))
1,605✔
244
        return session->external_reference();
225✔
245

612✔
246
    return nullptr;
1,380✔
247
}
1,380✔
248

249
std::shared_ptr<SyncSession> SyncManager::get_session(std::shared_ptr<DB> db, const RealmConfig& config)
250
{
1,574✔
251
    auto& client = get_sync_client(); // Throws
1,574✔
252
#ifndef __EMSCRIPTEN__
1,574✔
253
    auto path = db->get_path();
1,574✔
254
    REALM_ASSERT_EX(path == config.path, path, config.path);
1,574✔
255
#else
256
    auto path = config.path;
257
#endif
258
    REALM_ASSERT(config.sync_config);
1,574✔
259

701✔
260
    util::CheckedUniqueLock lock(m_session_mutex);
1,574✔
261
    if (auto session = get_existing_session_locked(path)) {
1,574✔
262
        return session->external_reference();
8✔
263
    }
8✔
264

697✔
265
    auto shared_session = SyncSession::create(client, std::move(db), config, this);
1,566✔
266
    m_sessions[path] = shared_session;
1,566✔
267

697✔
268
    // Create the external reference immediately to ensure that the session will become
697✔
269
    // inactive if an exception is thrown in the following code.
697✔
270
    return shared_session->external_reference();
1,566✔
271
}
1,566✔
272

273
bool SyncManager::has_existing_sessions()
274
{
13✔
275
    util::CheckedLockGuard lock(m_session_mutex);
13✔
276
    return do_has_existing_sessions();
13✔
277
}
13✔
278

279
bool SyncManager::do_has_existing_sessions()
280
{
4,438✔
281
    return std::any_of(m_sessions.begin(), m_sessions.end(), [](auto& element) {
2,184✔
282
        return element.second->existing_external_reference();
2✔
283
    });
2✔
284
}
4,438✔
285

286
void SyncManager::wait_for_sessions_to_terminate()
287
{
58✔
288
    auto& client = get_sync_client(); // Throws
58✔
289
    client.wait_for_session_terminations();
58✔
290
}
58✔
291

292
void SyncManager::unregister_session(const std::string& path)
293
{
2,496✔
294
    util::CheckedUniqueLock lock(m_session_mutex);
2,496✔
295
    auto it = m_sessions.find(path);
2,496✔
296
    if (it == m_sessions.end()) {
2,496✔
297
        // The session may already be unregistered. This always happens in the
26✔
298
        // SyncManager destructor, and can also happen due to multiple threads
26✔
299
        // tearing things down at once.
26✔
300
        return;
52✔
301
    }
52✔
302

1,069✔
303
    // Sync session teardown calls this function, so we need to be careful with
1,069✔
304
    // locking here. We need to unlock `m_session_mutex` before we do anything
1,069✔
305
    // which could result in a re-entrant call or we'll deadlock, which in this
1,069✔
306
    // function means unlocking before we destroy a `shared_ptr<SyncSession>`
1,069✔
307
    // (either the external reference or internal reference versions).
1,069✔
308
    // The external reference version will only be the final reference if
1,069✔
309
    // another thread drops a reference while we're in this function.
1,069✔
310
    // Dropping the final internal reference does not appear to ever actually
1,069✔
311
    // result in a recursive call to this function at the time this comment was
1,069✔
312
    // written, but releasing the lock in that case as well is still safer.
1,069✔
313

1,069✔
314
    if (auto existing_session = it->second->existing_external_reference()) {
2,444✔
315
        // We got here because the session entered the inactive state, but
399✔
316
        // there's still someone referencing it so we should leave it be. This
399✔
317
        // can happen if the user was logged out, or if all Realms using the
399✔
318
        // session were destroyed but the SDK user is holding onto the session.
399✔
319

399✔
320
        // Explicit unlock so that `existing_session`'s destructor runs after
399✔
321
        // the unlock for the reasons noted above
399✔
322
        lock.unlock();
931✔
323
        return;
931✔
324
    }
931✔
325

670✔
326
    // Remove the session from the map while holding the lock, but then defer
670✔
327
    // destroying it until after we unlock the mutex for the reasons noted above.
670✔
328
    auto session = m_sessions.extract(it);
1,513✔
329
    lock.unlock();
1,513✔
330
}
1,513✔
331

332
void SyncManager::update_sessions_for(SyncUser& user, SyncUser::State old_state, SyncUser::State new_state,
333
                                      std::string_view new_access_token)
334
{
14,101✔
335
    bool should_revive = old_state != SyncUser::State::LoggedIn && new_state == SyncUser::State::LoggedIn;
14,101✔
336
    bool should_stop = old_state == SyncUser::State::LoggedIn && new_state != SyncUser::State::LoggedIn;
14,101✔
337

7,018✔
338
    auto sessions = get_all_sessions_for(user);
14,101✔
339
    if (new_access_token.size()) {
14,101✔
340
        for (auto& session : sessions) {
4,620✔
341
            session->update_access_token(new_access_token);
18✔
342
        }
18✔
343
    }
9,263✔
344
    else if (should_revive) {
4,838✔
UNCOV
345
        for (auto& session : sessions) {
×
UNCOV
346
            session->revive_if_needed();
×
UNCOV
347
        }
×
UNCOV
348
    }
×
349
    else if (should_stop) {
4,838✔
350
        for (auto& session : sessions) {
56✔
351
            session->force_close();
31✔
352
        }
31✔
353
    }
82✔
354
}
14,101✔
355

356
void SyncManager::set_session_multiplexing(bool allowed)
357
{
4✔
358
    util::CheckedLockGuard lock(m_mutex);
4✔
359
    if (m_config.multiplex_sessions == allowed)
4✔
360
        return; // Already enabled, we can ignore
2✔
361

1✔
362
    if (m_sync_client)
2✔
UNCOV
363
        throw LogicError(ErrorCodes::IllegalOperation,
×
UNCOV
364
                         "Cannot enable session multiplexing after creating the sync client");
×
365

1✔
366
    m_config.multiplex_sessions = allowed;
2✔
367
}
2✔
368

369
SyncClient& SyncManager::get_sync_client() const
370
{
6,794✔
371
    util::CheckedLockGuard lock(m_mutex);
6,794✔
372
    if (!m_sync_client)
6,794✔
373
        m_sync_client = create_sync_client(); // Throws
4,427✔
374
    return *m_sync_client;
6,794✔
375
}
6,794✔
376

377
std::unique_ptr<SyncClient> SyncManager::create_sync_client() const
378
{
4,427✔
379
    REALM_ASSERT(m_logger_ptr);
4,427✔
380
    return std::make_unique<SyncClient>(m_logger_ptr, m_config, weak_from_this());
4,427✔
381
}
4,427✔
382

383
void SyncManager::close_all_sessions()
384
{
4,425✔
385
    // force_close() will call unregister_session(), which requires m_session_mutex,
2,177✔
386
    // so we need to iterate over them without holding the lock.
2,177✔
387
    decltype(m_sessions) sessions;
4,425✔
388
    {
4,425✔
389
        util::CheckedLockGuard lk(m_session_mutex);
4,425✔
390
        m_sessions.swap(sessions);
4,425✔
391
    }
4,425✔
392

2,177✔
393
    for (auto& [_, session] : sessions) {
2,203✔
394
        session->force_close();
53✔
395
    }
53✔
396

2,177✔
397
    get_sync_client().wait_for_session_terminations();
4,425✔
398
}
4,425✔
399

400
void SyncManager::set_sync_route(std::string sync_route, bool verified)
401
{
8,694✔
402
    REALM_ASSERT(!sync_route.empty()); // Cannot be set to empty string
8,694✔
403
    {
8,694✔
404
        util::CheckedLockGuard lock(m_mutex);
8,694✔
405
        m_sync_route = sync_route;
8,694✔
406
        m_sync_route_verified = verified;
8,694✔
407
    }
8,694✔
408
}
8,694✔
409

410
void SyncManager::restart_all_sessions()
UNCOV
411
{
×
412
    // Restart the sessions that are currently active
UNCOV
413
    auto sessions = get_all_sessions();
×
UNCOV
414
    for (auto& session : sessions) {
×
UNCOV
415
        session->restart_session();
×
UNCOV
416
    }
×
UNCOV
417
}
×
418

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

© 2025 Coveralls, Inc