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

realm / realm-core / github_pull_request_312964

19 Feb 2025 07:31PM UTC coverage: 90.814% (-0.3%) from 91.119%
github_pull_request_312964

Pull #8071

Evergreen

web-flow
Bump serialize-javascript and mocha

Bumps [serialize-javascript](https://github.com/yahoo/serialize-javascript) to 6.0.2 and updates ancestor dependency [mocha](https://github.com/mochajs/mocha). These dependencies need to be updated together.


Updates `serialize-javascript` from 6.0.0 to 6.0.2
- [Release notes](https://github.com/yahoo/serialize-javascript/releases)
- [Commits](https://github.com/yahoo/serialize-javascript/compare/v6.0.0...v6.0.2)

Updates `mocha` from 10.2.0 to 10.8.2
- [Release notes](https://github.com/mochajs/mocha/releases)
- [Changelog](https://github.com/mochajs/mocha/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mochajs/mocha/compare/v10.2.0...v10.8.2)

---
updated-dependencies:
- dependency-name: serialize-javascript
  dependency-type: indirect
- dependency-name: mocha
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #8071: Bump serialize-javascript and mocha

96552 of 179126 branches covered (53.9%)

212672 of 234185 relevant lines covered (90.81%)

3115802.0 hits per line

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

89.8
/src/realm/object-store/impl/realm_coordinator.cpp
1
////////////////////////////////////////////////////////////////////////////
2
//
3
// Copyright 2015 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/impl/realm_coordinator.hpp>
20

21
#include <realm/object-store/impl/collection_notifier.hpp>
22
#include <realm/object-store/impl/external_commit_helper.hpp>
23
#include <realm/object-store/impl/transact_log_handler.hpp>
24
#include <realm/object-store/impl/weak_realm_notifier.hpp>
25
#include <realm/object-store/audit.hpp>
26
#include <realm/object-store/binding_context.hpp>
27
#include <realm/object-store/object_schema.hpp>
28
#include <realm/object-store/object_store.hpp>
29
#include <realm/object-store/property.hpp>
30
#include <realm/object-store/schema.hpp>
31
#include <realm/object-store/thread_safe_reference.hpp>
32
#include <realm/object-store/util/scheduler.hpp>
33

34
#if REALM_ENABLE_SYNC
35
#include <realm/object-store/sync/async_open_task.hpp>
36
#include <realm/object-store/sync/sync_manager.hpp>
37
#include <realm/object-store/sync/sync_session.hpp>
38
#include <realm/object-store/sync/sync_user.hpp>
39
#include <realm/sync/history.hpp>
40
#include <realm/sync/noinst/client_history_impl.hpp>
41
#endif
42

43
#include <realm/db.hpp>
44
#include <realm/history.hpp>
45
#include <realm/string_data.hpp>
46
#include <realm/util/fifo_helper.hpp>
47
#include <realm/sync/config.hpp>
48

49
#include <algorithm>
50
#include <unordered_map>
51

52
using namespace realm;
53
using namespace realm::_impl;
54

55
static auto& s_coordinator_mutex = *new std::mutex;
56
static auto& s_coordinators_per_path = *new std::unordered_map<std::string, std::weak_ptr<RealmCoordinator>>;
57

58
std::shared_ptr<RealmCoordinator> RealmCoordinator::get_coordinator(StringData path)
59
{
27,537✔
60
    std::lock_guard<std::mutex> lock(s_coordinator_mutex);
27,537✔
61

62
    auto& weak_coordinator = s_coordinators_per_path[path];
27,537✔
63
    if (auto coordinator = weak_coordinator.lock()) {
27,537✔
64
        return coordinator;
17,761✔
65
    }
17,761✔
66

67
    auto coordinator = std::make_shared<RealmCoordinator>(Private());
9,776✔
68
    weak_coordinator = coordinator;
9,776✔
69
    return coordinator;
9,776✔
70
}
27,537✔
71

72
std::shared_ptr<RealmCoordinator>
73
RealmCoordinator::get_coordinator(const Realm::Config& config) NO_THREAD_SAFETY_ANALYSIS
74
{
68✔
75
    auto coordinator = get_coordinator(config.path);
68✔
76
    util::CheckedLockGuard lock(coordinator->m_realm_mutex);
68✔
77
    coordinator->set_config(config);
68✔
78
    coordinator->open_db();
68✔
79
    return coordinator;
68✔
80
}
68✔
81

82
std::shared_ptr<RealmCoordinator> RealmCoordinator::get_existing_coordinator(StringData path)
83
{
370✔
84
    std::lock_guard<std::mutex> lock(s_coordinator_mutex);
370✔
85
    return s_coordinators_per_path[path].lock();
370✔
86
}
370✔
87

88
void RealmCoordinator::set_config(const Realm::Config& config)
89
{
12,640✔
90
    if (config.encryption_key.data() && config.encryption_key.size() != 64)
12,640✔
91
        throw InvalidEncryptionKey();
1✔
92
    if (config.schema_mode == SchemaMode::Immutable && config.sync_config)
12,639✔
93
        throw InvalidArgument(ErrorCodes::IllegalCombination,
×
94
                              "Synchronized Realms cannot be opened in immutable mode");
×
95
    if ((config.schema_mode == SchemaMode::AdditiveDiscovered ||
12,639✔
96
         config.schema_mode == SchemaMode::AdditiveExplicit) &&
12,639✔
97
        config.migration_function)
12,639✔
98
        throw InvalidArgument(ErrorCodes::IllegalCombination,
2✔
99
                              "Realms opened in Additive-only schema mode do not use a migration function");
2✔
100
    if (config.schema_mode == SchemaMode::Immutable && config.migration_function)
12,637✔
101
        throw InvalidArgument(ErrorCodes::IllegalCombination,
1✔
102
                              "Realms opened in immutable mode do not use a migration function");
1✔
103
    if (config.schema_mode == SchemaMode::ReadOnly && config.migration_function)
12,636✔
104
        throw InvalidArgument(ErrorCodes::IllegalCombination,
1✔
105
                              "Realms opened in read-only mode do not use a migration function");
1✔
106
    if (config.schema_mode == SchemaMode::Immutable && config.initialization_function)
12,635✔
107
        throw InvalidArgument(ErrorCodes::IllegalCombination,
1✔
108
                              "Realms opened in immutable mode do not use an initialization function");
1✔
109
    if (config.schema_mode == SchemaMode::ReadOnly && config.initialization_function)
12,634✔
110
        throw InvalidArgument(ErrorCodes::IllegalCombination,
1✔
111
                              "Realms opened in read-only mode do not use an initialization function");
1✔
112
    if (config.schema && config.schema_version == ObjectStore::NotVersioned)
12,633✔
113
        throw InvalidArgument(ErrorCodes::IllegalCombination,
1✔
114
                              "A schema version must be specified when the schema is specified");
1✔
115
    if (!config.realm_data.is_null() && (!config.immutable() || !config.in_memory))
12,632✔
116
        throw InvalidArgument(
1✔
117
            ErrorCodes::IllegalCombination,
1✔
118
            "In-memory realms initialized from memory buffers can only be opened in read-only mode");
1✔
119
    if (!config.realm_data.is_null() && !config.path.empty())
12,631✔
120
        throw InvalidArgument(ErrorCodes::IllegalCombination, "Specifying both memory buffer and path is invalid");
1✔
121
    if (!config.realm_data.is_null() && !config.encryption_key.empty())
12,630✔
122
        throw InvalidArgument(ErrorCodes::IllegalCombination, "Memory buffers do not support encryption");
1✔
123
    if (config.in_memory && !config.encryption_key.empty()) {
12,629✔
124
        throw InvalidArgument(ErrorCodes::IllegalCombination, "Encryption is not supported for in-memory realms");
1✔
125
    }
1✔
126
    // ResetFile also won't use the migration function, but specifying one is
127
    // allowed to simplify temporarily switching modes during development
128

129
#if REALM_ENABLE_SYNC
12,628✔
130
    if (config.sync_config) {
12,628✔
131
        if (config.sync_config->flx_sync_requested && !config.sync_config->partition_value.empty()) {
911✔
132
            throw InvalidArgument(ErrorCodes::IllegalCombination,
1✔
133
                                  "Cannot specify a partition value when flexible sync is enabled");
1✔
134
        }
1✔
135
        if (!config.sync_config->user) {
910✔
136
            throw InvalidArgument(ErrorCodes::IllegalCombination,
×
137
                                  "A user must be provided to open a synchronized Realm.");
×
138
        }
×
139
    }
910✔
140
#endif
12,627✔
141

142
    bool no_existing_realm =
12,627✔
143
        std::all_of(begin(m_weak_realm_notifiers), end(m_weak_realm_notifiers), [](auto& notifier) {
12,627✔
144
            return notifier.expired();
2,828✔
145
        });
2,828✔
146
    if (no_existing_realm) {
12,627✔
147
        m_config = config;
9,799✔
148
        m_config.scheduler = nullptr;
9,799✔
149
    }
9,799✔
150
    else {
2,828✔
151
        if (m_config.immutable() != config.immutable()) {
2,828✔
152
            throw LogicError(
×
153
                ErrorCodes::MismatchedConfig,
×
154
                util::format("Realm at path '%1' already opened with different read permissions.", config.path));
×
155
        }
×
156
        if (m_config.in_memory != config.in_memory) {
2,828✔
157
            throw LogicError(
1✔
158
                ErrorCodes::MismatchedConfig,
1✔
159
                util::format("Realm at path '%1' already opened with different inMemory settings.", config.path));
1✔
160
        }
1✔
161
        if (m_config.encryption_key != config.encryption_key) {
2,827✔
162
            throw LogicError(
×
163
                ErrorCodes::MismatchedConfig,
×
164
                util::format("Realm at path '%1' already opened with a different encryption key.", config.path));
×
165
        }
×
166
        if (m_config.schema_mode != config.schema_mode) {
2,827✔
167
            throw LogicError(
1✔
168
                ErrorCodes::MismatchedConfig,
1✔
169
                util::format("Realm at path '%1' already opened with a different schema mode.", config.path));
1✔
170
        }
1✔
171
        util::CheckedLockGuard lock(m_schema_cache_mutex);
2,826✔
172
        if (config.schema && m_schema_version != ObjectStore::NotVersioned &&
2,826✔
173
            m_schema_version != config.schema_version) {
2,826✔
174
            throw LogicError(
1✔
175
                ErrorCodes::MismatchedConfig,
1✔
176
                util::format("Realm at path '%1' already opened with different schema version.", config.path));
1✔
177
        }
1✔
178

179
#if REALM_ENABLE_SYNC
2,825✔
180
        if (bool(m_config.sync_config) != bool(config.sync_config)) {
2,825✔
181
            throw LogicError(
×
182
                ErrorCodes::MismatchedConfig,
×
183
                util::format("Realm at path '%1' already opened with different sync configurations.", config.path));
×
184
        }
×
185

186
        if (config.sync_config) {
2,825✔
187
            auto old_user = m_config.sync_config->user;
191✔
188
            auto new_user = config.sync_config->user;
191✔
189
            if (old_user != new_user) {
191✔
190
                throw LogicError(
×
191
                    ErrorCodes::MismatchedConfig,
×
192
                    util::format("Realm at path '%1' already opened with different sync user.", config.path));
×
193
            }
×
194
            if (m_config.sync_config->partition_value != config.sync_config->partition_value) {
191✔
195
                throw LogicError(
×
196
                    ErrorCodes::MismatchedConfig,
×
197
                    util::format("Realm at path '%1' already opened with different partition value.", config.path));
×
198
            }
×
199
            if (m_config.sync_config->flx_sync_requested != config.sync_config->flx_sync_requested) {
191✔
200
                throw LogicError(ErrorCodes::MismatchedConfig,
×
201
                                 util::format("Realm at path '%1' already opened in a different synchronization mode",
×
202
                                              config.path));
×
203
            }
×
204
        }
191✔
205
#endif
2,825✔
206
        // Mixing cached and uncached Realms is allowed
207
        m_config.cache = config.cache;
2,825✔
208

209
        // Realm::update_schema() handles complaining about schema mismatches
210
    }
2,825✔
211
}
12,627✔
212

213
std::shared_ptr<Realm> RealmCoordinator::get_cached_realm(Realm::Config const& config,
214
                                                          std::shared_ptr<util::Scheduler> scheduler)
215
{
101✔
216
    if (!config.cache)
101✔
217
        return nullptr;
97✔
218
    util::CheckedUniqueLock lock(m_realm_mutex);
4✔
219
    return do_get_cached_realm(config, scheduler);
4✔
220
}
101✔
221

222
std::shared_ptr<Realm> RealmCoordinator::do_get_cached_realm(Realm::Config const& config,
223
                                                             std::shared_ptr<util::Scheduler> scheduler)
224
{
17,142✔
225
    if (!config.cache)
17,142✔
226
        return nullptr;
17,114✔
227

228
    if (!scheduler) {
28✔
229
        scheduler = config.scheduler;
20✔
230
    }
20✔
231

232
    if (!scheduler)
28✔
233
        return nullptr;
×
234

235
    for (auto& cached_realm : m_weak_realm_notifiers) {
28✔
236
        if (!cached_realm.is_cached_for_scheduler(scheduler))
27✔
237
            continue;
17✔
238
        // can be null if we jumped in between ref count hitting zero and
239
        // unregister_realm() getting the lock
240
        if (auto realm = cached_realm.realm()) {
10✔
241
            // If the file is uninitialized and was opened without a schema,
242
            // do the normal schema init
243
            if (realm->schema_version() == ObjectStore::NotVersioned)
10✔
244
                break;
2✔
245

246
            // Otherwise if we have a realm schema it needs to be an exact
247
            // match (even having the same properties but in different
248
            // orders isn't good enough)
249
            if (config.schema && realm->schema() != *config.schema)
8✔
250
                throw LogicError(
×
251
                    ErrorCodes::MismatchedConfig,
×
252
                    util::format("Realm at path '%1' already opened on current thread with different schema.",
×
253
                                 config.path));
×
254

255
            return realm;
8✔
256
        }
8✔
257
    }
10✔
258
    return nullptr;
20✔
259
}
28✔
260

261
std::shared_ptr<Realm> RealmCoordinator::get_realm(Realm::Config config, util::Optional<VersionID> version)
262
{
12,478✔
263
    REALM_ASSERT(!version || *version != VersionID());
12,478✔
264
    if (!config.scheduler)
12,478✔
265
        config.scheduler = version ? util::Scheduler::make_frozen(*version) : util::Scheduler::make_default();
11,951✔
266
    // realm must be declared before lock so that the mutex is released before
267
    // we release the strong reference to realm, as Realm's destructor may want
268
    // to acquire the same lock
269
    std::shared_ptr<Realm> realm;
12,478✔
270
    util::CheckedUniqueLock lock(m_realm_mutex);
12,478✔
271
    set_config(config);
12,478✔
272
    if ((realm = do_get_cached_realm(config))) {
12,478✔
273
        REALM_ASSERT(!version || realm->read_transaction_version() == *version);
6✔
274
        return realm;
6✔
275
    }
6✔
276
    do_get_realm(std::move(config), realm, version, lock);
12,472✔
277
    if (version) {
12,472✔
278
        realm->read_group();
189✔
279
    }
189✔
280
    return realm;
12,472✔
281
}
12,478✔
282

283
std::shared_ptr<Realm> RealmCoordinator::get_realm(std::shared_ptr<util::Scheduler> scheduler, bool first_time_open)
284
{
4,004✔
285
    std::shared_ptr<Realm> realm;
4,004✔
286
    util::CheckedUniqueLock lock(m_realm_mutex);
4,004✔
287
    auto config = m_config;
4,004✔
288
    config.scheduler = scheduler ? scheduler : util::Scheduler::make_default();
4,004✔
289
    if ((realm = do_get_cached_realm(config))) {
4,004✔
290
        return realm;
×
291
    }
×
292
    do_get_realm(std::move(config), realm, none, lock, first_time_open);
4,004✔
293
    return realm;
4,004✔
294
}
4,004✔
295

296
std::shared_ptr<Realm> RealmCoordinator::freeze_realm(const Realm& source_realm)
297
{
672✔
298
    std::shared_ptr<Realm> realm;
672✔
299
    util::CheckedUniqueLock lock(m_realm_mutex);
672✔
300

301
    auto version = source_realm.read_transaction_version();
672✔
302
    auto scheduler = util::Scheduler::make_frozen(version);
672✔
303
    if ((realm = do_get_cached_realm(source_realm.config(), scheduler))) {
672✔
304
        return realm;
1✔
305
    }
1✔
306

307
    auto config = source_realm.config();
671✔
308
    config.scheduler = scheduler;
671✔
309
    realm = Realm::make_shared_realm(std::move(config), version, shared_from_this());
671✔
310
    Realm::Internal::copy_schema(*realm, source_realm);
671✔
311
    m_weak_realm_notifiers.emplace_back(realm, config.cache);
671✔
312
    return realm;
671✔
313
}
672✔
314

315
ThreadSafeReference RealmCoordinator::get_unbound_realm()
316
{
137✔
317
    std::shared_ptr<Realm> realm;
137✔
318
    util::CheckedUniqueLock lock(m_realm_mutex);
137✔
319
    do_get_realm(RealmConfig(m_config), realm, none, lock);
137✔
320
    return ThreadSafeReference(realm);
137✔
321
}
137✔
322

323
void RealmCoordinator::do_get_realm(RealmConfig&& config, std::shared_ptr<Realm>& realm,
324
                                    util::Optional<VersionID> version, util::CheckedUniqueLock& realm_lock,
325
                                    bool first_time_open)
326
{
16,597✔
327
    const auto db_created = open_db();
16,597✔
328
#ifdef REALM_ENABLE_SYNC
16,597✔
329
    SyncConfig::SubscriptionInitializerCallback subscription_function = nullptr;
16,597✔
330
    bool rerun_on_open = false;
16,597✔
331
    if (config.sync_config && config.sync_config->flx_sync_requested &&
16,597✔
332
        config.sync_config->subscription_initializer) {
16,597✔
333
        subscription_function = config.sync_config->subscription_initializer;
74✔
334
        rerun_on_open = config.sync_config->rerun_init_subscription_on_open;
74✔
335
    }
74✔
336
#else
337
    static_cast<void>(first_time_open);
338
    static_cast<void>(db_created);
339
#endif
340

341
    auto schema = std::move(config.schema);
16,597✔
342
    auto migration_function = std::move(config.migration_function);
16,597✔
343
    auto initialization_function = std::move(config.initialization_function);
16,597✔
344
    config.schema = {};
16,597✔
345

346
    realm = Realm::make_shared_realm(std::move(config), version, shared_from_this());
16,597✔
347
    m_weak_realm_notifiers.emplace_back(realm, config.cache);
16,597✔
348

349
#ifdef REALM_ENABLE_SYNC
16,597✔
350
    if (m_sync_session && m_sync_session->user()->is_logged_in())
16,597✔
351
        m_sync_session->revive_if_needed();
927✔
352

353
    if (realm->config().audit_config) {
16,597✔
354
        if (m_audit_context)
×
355
            m_audit_context->update_metadata(realm->config().audit_config->metadata);
×
356
        else
×
357
            m_audit_context = make_audit_context(m_db, realm->config());
×
358
    }
×
359
#else
360
    if (realm->config().audit_config)
361
        REALM_TERMINATE("Cannot use Audit interface if Realm Core is built without Sync");
362
#endif
363

364
    // Cached frozen Realms need to initialize their schema before releasing
365
    // the lock as otherwise they could be read from the cache on another thread
366
    // before the schema initialization happens. They'll never perform a write
367
    // transaction, so unlike with live Realms this is safe to do.
368
    if (config.cache && version && schema) {
16,597!
369
        realm->update_schema(std::move(*schema));
×
370
        schema.reset();
×
371
    }
×
372

373
    realm_lock.unlock_unchecked();
16,597✔
374
    if (schema) {
16,597✔
375
        realm->update_schema(std::move(*schema), config.schema_version, std::move(migration_function),
13,423✔
376
                             std::move(initialization_function));
13,423✔
377
    }
13,423✔
378

379
#ifdef REALM_ENABLE_SYNC
16,597✔
380
    // run subscription initializer if the SDK has instructed core to do so. The subscription callback will be run if:
381
    // 1. this is the first time we are creating the realm file
382
    // 2. the database was already created, but this is the first time we are opening the db and the flag
383
    // rerun_on_open was set
384
    if (subscription_function) {
16,597✔
385
        const auto current_subscription = realm->get_latest_subscription_set();
72✔
386
        const auto subscription_version = current_subscription.version();
72✔
387
        // in case we are hitting this check while during a normal open, we need to take in
388
        // consideration if the db was created during this call. Since this may be the first time
389
        // we are actually creating a realm. For async open this does not apply, in fact db_created
390
        // will always be false.
391
        if (!first_time_open)
72✔
392
            first_time_open = db_created;
50✔
393
        if (subscription_version == 0 || (first_time_open && rerun_on_open)) {
72✔
394
            bool was_in_read = realm->is_in_read_transaction();
37✔
395
            subscription_function(realm);
37✔
396
            if (!was_in_read)
37✔
397
                realm->invalidate();
37✔
398
        }
37✔
399
    }
72✔
400
#endif
16,597✔
401
}
16,597✔
402

403
void RealmCoordinator::bind_to_context(Realm& realm)
404
{
100✔
405
    util::CheckedLockGuard lock(m_realm_mutex);
100✔
406
    for (auto& cached_realm : m_weak_realm_notifiers) {
136✔
407
        if (!cached_realm.is_for_realm(&realm))
136✔
408
            continue;
36✔
409
        cached_realm.bind_to_scheduler();
100✔
410
        return;
100✔
411
    }
136✔
412
    REALM_TERMINATE("Invalid Realm passed to bind_to_context()");
413
}
×
414

415
#if REALM_ENABLE_SYNC
416
std::shared_ptr<AsyncOpenTask> RealmCoordinator::get_synchronized_realm(Realm::Config config)
417
{
94✔
418
    if (!config.sync_config)
94✔
419
        throw LogicError(ErrorCodes::IllegalOperation,
×
420
                         "This method is only available for fully synchronized Realms.");
×
421

422
    util::CheckedLockGuard lock(m_realm_mutex);
94✔
423
    set_config(config);
94✔
424
    const auto db_open_first_time = open_db();
94✔
425
    return std::make_shared<AsyncOpenTask>(AsyncOpenTask::Private(), shared_from_this(), m_sync_session,
94✔
426
                                           db_open_first_time);
94✔
427
}
94✔
428

429
#endif
430

431
bool RealmCoordinator::open_db()
432
{
16,769✔
433
    if (m_db)
16,769✔
434
        return false;
7,002✔
435

436
#if REALM_ENABLE_SYNC
9,767✔
437
    if (m_config.sync_config) {
9,767✔
438
        REALM_ASSERT(m_config.sync_config->user);
693✔
439
        // If we previously opened this Realm, we may have a lingering sync
440
        // session which outlived its RealmCoordinator. If that happens we
441
        // want to reuse it instead of creating a new DB.
442
        if (auto sync_manager = m_config.sync_config->user->sync_manager()) {
693✔
443
            m_sync_session = sync_manager->get_existing_session(m_config.path);
691✔
444
        }
691✔
445
        if (m_sync_session) {
693✔
446
            m_db = SyncSession::Internal::get_db(*m_sync_session);
12✔
447
            init_external_helpers();
12✔
448
            return false;
12✔
449
        }
12✔
450
    }
693✔
451
#endif
9,755✔
452

453
    bool server_synchronization_mode = m_config.sync_config || m_config.force_sync_history;
9,755✔
454
    bool schema_mode_reset_file =
9,755✔
455
        m_config.schema_mode == SchemaMode::SoftResetFile || m_config.schema_mode == SchemaMode::HardResetFile;
9,755✔
456
    try {
9,755✔
457
        if (m_config.immutable() && m_config.realm_data) {
9,755✔
458
            m_db = DB::create(m_config.realm_data, false);
1✔
459
            return true;
1✔
460
        }
1✔
461
        std::unique_ptr<Replication> history;
9,754✔
462
        if (server_synchronization_mode) {
9,754✔
463
#if REALM_ENABLE_SYNC
4,334✔
464
            bool apply_server_changes = !m_config.sync_config || m_config.sync_config->apply_server_changes;
4,334✔
465
            history = std::make_unique<sync::ClientReplication>(apply_server_changes);
4,334✔
466
#else
467
            REALM_TERMINATE("Realm was not built with sync enabled");
468
#endif
469
        }
4,334✔
470
        else if (!m_config.immutable()) {
5,420✔
471
            history = make_in_realm_history();
5,390✔
472
        }
5,390✔
473

474
        DBOptions options;
9,754✔
475
#ifndef __EMSCRIPTEN__
9,754✔
476
        options.enable_async_writes = true;
9,754✔
477
#endif
9,754✔
478
        options.durability = m_config.in_memory ? DBOptions::Durability::MemOnly : DBOptions::Durability::Full;
9,754✔
479
        options.is_immutable = m_config.immutable();
9,754✔
480
        options.logger = util::Logger::get_default_logger();
9,754✔
481

482
        if (!m_config.fifo_files_fallback_path.empty()) {
9,754✔
483
            options.temp_dir = util::normalize_dir(m_config.fifo_files_fallback_path);
2✔
484
        }
2✔
485
        options.encryption_key = m_config.encryption_key.data();
9,754✔
486
        options.allow_file_format_upgrade = !m_config.disable_format_upgrade && !schema_mode_reset_file;
9,754✔
487
        options.clear_on_invalid_file = m_config.clear_on_invalid_file;
9,754✔
488
        if (history) {
9,754✔
489
            options.backup_at_file_format_change = m_config.backup_at_file_format_change;
9,724✔
490
#ifdef __EMSCRIPTEN__
491
            // Force the DB to be created in memory-only mode, ignoring the filesystem path supplied in the config.
492
            // This is so we can run an SDK on top without having to solve the browser persistence problem yet,
493
            // or teach RealmConfig and SDKs about pure in-memory realms.
494
            m_db = DB::create_in_memory(std::move(history), m_config.path, options);
495
#else
496
            if (m_config.path.size()) {
9,724✔
497
                m_db = DB::create(std::move(history), m_config.path, options);
5,472✔
498
            }
5,472✔
499
            else {
4,252✔
500
                m_db = DB::create(std::move(history), options);
4,252✔
501
            }
4,252✔
502
#endif
9,724✔
503
        }
9,724✔
504
        else {
30✔
505
            options.no_create = true;
30✔
506
            m_db = DB::create(m_config.path, options);
30✔
507
        }
30✔
508
    }
9,754✔
509
    catch (realm::FileFormatUpgradeRequired const&) {
9,755✔
510
        if (!schema_mode_reset_file) {
×
511
            throw;
×
512
        }
×
513
        util::File::remove(m_config.path);
×
514
        return open_db();
×
515
    }
×
516
    catch (UnsupportedFileFormatVersion const&) {
9,755✔
517
        if (!schema_mode_reset_file) {
×
518
            throw;
×
519
        }
×
520
        util::File::remove(m_config.path);
×
521
        return open_db();
×
522
    }
×
523

524
    if (m_config.should_compact_on_launch_function) {
9,748✔
525
        size_t free_space = 0;
8✔
526
        size_t used_space = 0;
8✔
527
        if (auto tr = m_db->start_write(true)) {
8✔
528
            tr->commit();
8✔
529
            m_db->get_stats(free_space, used_space);
8✔
530
        }
8✔
531
        if (free_space > 0 && m_config.should_compact_on_launch_function(free_space + used_space, used_space))
8✔
532
            m_db->compact();
4✔
533
    }
8✔
534

535
    init_external_helpers();
9,748✔
536
    return true;
9,748✔
537
}
9,755✔
538

539
void RealmCoordinator::init_external_helpers()
540
{
9,760✔
541
    // There's a circular dependency between SyncSession and ExternalCommitHelper
542
    // where sync commits notify ECH and other commits notify sync via ECH. This
543
    // happens on background threads, so to avoid needing locking on every access
544
    // we have to wire things up in a specific order.
545
#if REALM_ENABLE_SYNC
9,760✔
546
    // We may have reused an existing sync session that outlived its original
547
    // RealmCoordinator. If not, we need to create a new one now.
548
    if (m_config.sync_config && !m_sync_session) {
9,760✔
549
        if (!m_config.sync_config->user || m_config.sync_config->user->state() == SyncUser::State::Removed) {
681✔
550
            throw app::AppError(
2✔
551
                ErrorCodes::ClientUserNotFound,
2✔
552
                util::format("Cannot start a sync session for user '%1' because this user has been removed.",
2✔
553
                             m_config.sync_config->user->user_id()));
2✔
554
        }
2✔
555
        if (auto sync_manager = m_config.sync_config->user->sync_manager()) {
679✔
556
            m_sync_session = sync_manager->get_session(m_db, m_config);
679✔
557
        }
679✔
558
    }
679✔
559
#endif
9,758✔
560

561
    if (!m_notifier && !m_config.immutable() && m_config.automatic_change_notifications) {
9,758✔
562
        try {
3,176✔
563
            m_notifier = std::make_unique<ExternalCommitHelper>(*this, m_config);
3,176✔
564
        }
3,176✔
565
        catch (std::system_error const& ex) {
3,176✔
566
            throw FileAccessError(ErrorCodes::FileOperationFailed,
×
567
                                  util::format("Failed to create ExternalCommitHelper: %1", ex.what()), get_path(),
×
568
                                  ex.code().value());
×
569
        }
×
570
    }
3,176✔
571
    m_db->add_commit_listener(this);
9,757✔
572
}
9,757✔
573

574
void RealmCoordinator::close()
575
{
10✔
576
    m_db->close();
10✔
577
    m_db = nullptr;
10✔
578
}
10✔
579

580
void RealmCoordinator::delete_and_reopen()
581
{
10✔
582
    util::CheckedLockGuard lock(m_realm_mutex);
10✔
583
    close();
10✔
584
    util::File::remove(m_config.path);
10✔
585
    open_db();
10✔
586
}
10✔
587

588
TransactionRef RealmCoordinator::begin_read(VersionID version, bool frozen_transaction)
589
{
41,972✔
590
    REALM_ASSERT(m_db);
41,972✔
591
    return frozen_transaction ? m_db->start_frozen(version) : m_db->start_read(version);
41,972✔
592
}
41,972✔
593

594
uint64_t RealmCoordinator::get_schema_version() const noexcept
595
{
×
596
    util::CheckedLockGuard lock(m_schema_cache_mutex);
×
597
    return m_schema_version;
×
598
}
×
599

600
bool RealmCoordinator::get_cached_schema(Schema& schema, uint64_t& schema_version,
601
                                         uint64_t& transaction) const noexcept
602
{
26,298✔
603
    util::CheckedLockGuard lock(m_schema_cache_mutex);
26,298✔
604
    if (!m_cached_schema)
26,298✔
605
        return false;
19,089✔
606
    schema = *m_cached_schema;
7,209✔
607
    schema_version = m_schema_version;
7,209✔
608
    transaction = m_schema_transaction_version_max;
7,209✔
609
    return true;
7,209✔
610
}
26,298✔
611

612
void RealmCoordinator::cache_schema(Schema const& new_schema, uint64_t new_schema_version,
613
                                    uint64_t transaction_version)
614
{
21,946✔
615
    util::CheckedLockGuard lock(m_schema_cache_mutex);
21,946✔
616
    if (transaction_version < m_schema_transaction_version_max)
21,946✔
617
        return;
159✔
618
    if (new_schema.empty() || new_schema_version == ObjectStore::NotVersioned)
21,787✔
619
        return;
9,510✔
620

621
    m_cached_schema = new_schema;
12,277✔
622
    m_schema_version = new_schema_version;
12,277✔
623
    m_schema_transaction_version_min = transaction_version;
12,277✔
624
    m_schema_transaction_version_max = transaction_version;
12,277✔
625
}
12,277✔
626

627
void RealmCoordinator::clear_schema_cache_and_set_schema_version(uint64_t new_schema_version)
628
{
9,580✔
629
    util::CheckedLockGuard lock(m_schema_cache_mutex);
9,580✔
630
    m_cached_schema = util::none;
9,580✔
631
    m_schema_version = new_schema_version;
9,580✔
632
}
9,580✔
633

634
void RealmCoordinator::advance_schema_cache(uint64_t previous, uint64_t next)
635
{
59,462✔
636
    util::CheckedLockGuard lock(m_schema_cache_mutex);
59,462✔
637
    if (!m_cached_schema)
59,462✔
638
        return;
9,413✔
639
    REALM_ASSERT(previous <= m_schema_transaction_version_max);
50,049✔
640
    if (next < m_schema_transaction_version_min)
50,049✔
641
        return;
1✔
642
    m_schema_transaction_version_min = std::min(previous, m_schema_transaction_version_min);
50,048✔
643
    m_schema_transaction_version_max = std::max(next, m_schema_transaction_version_max);
50,048✔
644
}
50,048✔
645

646
RealmCoordinator::RealmCoordinator(Private) {}
9,776✔
647

648
RealmCoordinator::~RealmCoordinator()
649
{
9,776✔
650
    {
9,776✔
651
        std::lock_guard<std::mutex> coordinator_lock(s_coordinator_mutex);
9,776✔
652
        for (auto it = s_coordinators_per_path.begin(); it != s_coordinators_per_path.end();) {
22,850✔
653
            if (it->second.expired()) {
13,074✔
654
                it = s_coordinators_per_path.erase(it);
9,755✔
655
            }
9,755✔
656
            else {
3,319✔
657
                ++it;
3,319✔
658
            }
3,319✔
659
        }
13,074✔
660
    }
9,776✔
661

662
    if (m_db) {
9,776✔
663
        m_db->remove_commit_listener(this);
9,751✔
664
    }
9,751✔
665

666
    // Waits for the worker thread to join
667
    m_notifier.reset();
9,776✔
668

669
    // If there's any active NotificationTokens they'll keep the notifiers alive,
670
    // so tell the notifiers to release their Transactions so that the DB can
671
    // be closed immediately.
672
    // No locking needed here because the worker thread is gone
673
    for (auto& notifier : m_new_notifiers)
9,776✔
674
        notifier->release_data();
27✔
675
    for (auto& notifier : m_notifiers)
9,776✔
676
        notifier->release_data();
42✔
677
}
9,776✔
678

679
void RealmCoordinator::unregister_realm(Realm* realm)
680
{
17,365✔
681
    util::CheckedLockGuard lock(m_realm_mutex);
17,365✔
682
    // Normally results notifiers are cleaned up by the background worker thread
683
    // but if that's disabled we need to ensure that any notifiers from this
684
    // Realm get cleaned up
685
    if (!m_config.automatic_change_notifications) {
17,365✔
686
        util::CheckedLockGuard lock(m_notifier_mutex);
13,261✔
687
        clean_up_dead_notifiers();
13,261✔
688
    }
13,261✔
689
    {
17,365✔
690
        auto new_end = remove_if(begin(m_weak_realm_notifiers), end(m_weak_realm_notifiers), [=](auto& notifier) {
24,149✔
691
            return notifier.expired() || notifier.is_for_realm(realm);
24,149✔
692
        });
24,149✔
693
        m_weak_realm_notifiers.erase(new_end, end(m_weak_realm_notifiers));
17,365✔
694
    }
17,365✔
695
}
17,365✔
696

697
// Thread-safety analysis doesn't reasonably handle calling functions on different
698
// instances of this type
699
void RealmCoordinator::clear_cache() NO_THREAD_SAFETY_ANALYSIS
700
{
26✔
701
    std::vector<std::shared_ptr<RealmCoordinator>> coordinators;
26✔
702
    {
26✔
703
        std::lock_guard<std::mutex> lock(s_coordinator_mutex);
26✔
704
        for (auto& weak_coordinator : s_coordinators_per_path) {
26✔
705
            if (auto coordinator = weak_coordinator.second.lock()) {
26✔
706
                coordinators.push_back(coordinator);
26✔
707
            }
26✔
708
        }
26✔
709
        s_coordinators_per_path.clear();
26✔
710
    }
26✔
711

712
    for (auto& coordinator : coordinators) {
26✔
713
        coordinator->m_notifier = nullptr;
26✔
714

715
        std::vector<std::shared_ptr<Realm>> realms_to_close;
26✔
716
        {
26✔
717
            // Gather a list of all of the realms which will be removed
718
            util::CheckedLockGuard lock(coordinator->m_realm_mutex);
26✔
719
            for (auto& weak_realm_notifier : coordinator->m_weak_realm_notifiers) {
26✔
720
                if (auto realm = weak_realm_notifier.realm()) {
24✔
721
                    realms_to_close.push_back(realm);
24✔
722
                }
24✔
723
            }
24✔
724
        }
26✔
725

726
        // Close all of the previously cached Realms. This can't be done while
727
        // locks are held as it may try to re-lock them.
728
        for (auto& realm : realms_to_close)
26✔
729
            realm->close();
24✔
730
    }
26✔
731
}
26✔
732

733
void RealmCoordinator::clear_all_caches()
734
{
53✔
735
    std::vector<std::weak_ptr<RealmCoordinator>> to_clear;
53✔
736
    {
53✔
737
        std::lock_guard<std::mutex> lock(s_coordinator_mutex);
53✔
738
        for (auto iter : s_coordinators_per_path) {
53✔
739
            to_clear.push_back(iter.second);
26✔
740
        }
26✔
741
    }
53✔
742
    for (auto weak_coordinator : to_clear) {
53✔
743
        if (auto coordinator = weak_coordinator.lock()) {
26✔
744
            coordinator->clear_cache();
26✔
745
        }
26✔
746
    }
26✔
747
}
53✔
748

749
void RealmCoordinator::assert_no_open_realms() noexcept
750
{
519✔
751
#ifdef REALM_DEBUG
519✔
752
    std::lock_guard<std::mutex> lock(s_coordinator_mutex);
519✔
753
    REALM_ASSERT(s_coordinators_per_path.empty());
519✔
754
#endif
519✔
755
}
519✔
756

757
void RealmCoordinator::wake_up_notifier_worker()
758
{
5,358✔
759
    if (m_notifier) {
5,358✔
760
        // FIXME: this wakes up the notification workers for all processes and
761
        // not just us. This might be worth optimizing in the future.
762
        m_notifier->notify_others();
69✔
763
    }
69✔
764
}
5,358✔
765

766
void RealmCoordinator::commit_write(Realm& realm, bool commit_to_disk)
767
{
31,185✔
768
    REALM_ASSERT(!m_config.immutable());
31,185✔
769
    REALM_ASSERT(realm.is_in_transaction());
31,185✔
770

771
    Transaction& tr = Realm::Internal::get_transaction(realm);
31,185✔
772
    VersionID new_version;
31,185✔
773
    {
31,185✔
774
        // Need to acquire this lock before committing or another process could
775
        // perform a write and notify us before we get the chance to set the
776
        // skip version
777
        util::CheckedLockGuard l(m_notifier_mutex);
31,185✔
778
        new_version = tr.commit_and_continue_as_read(commit_to_disk);
31,185✔
779

780
        // The skip version must always be the notifier transaction's current
781
        // version plus one, as we can only skip a prefix and not intermediate
782
        // transactions. If we have a notifier for the current Realm, then we
783
        // waited until it finished running in begin_transaction() and this
784
        // invariant holds. If we don't have any notifiers then we don't need
785
        // to set the skip version, but more importantly *can't* because we
786
        // didn't block when starting the write and the notifier transaction
787
        // may still be on an older version.
788
        //
789
        // Note that this relies on the fact that callbacks cannot be added from
790
        // within write transactions. If they could be, we could hit this point
791
        // with an implicit-created notifier which ran (and so is in m_notifiers
792
        // and not m_new_notifiers) but didn't have a callback at the start of
793
        // the write so we didn't block for it then, but does now have a callback.
794
        // If we add support for that, we'll need to update this logic.
795
        bool have_notifiers = std::any_of(m_notifiers.begin(), m_notifiers.end(), [&](auto&& notifier) {
31,185✔
796
            return notifier->is_for_realm(realm) && notifier->have_callbacks();
5,426✔
797
        });
5,426✔
798
        if (have_notifiers) {
31,185✔
799
            REALM_ASSERT(!m_notifier_skip_version);
1,197✔
800
            REALM_ASSERT(m_notifier_transaction);
1,197✔
801
            REALM_ASSERT_3(m_notifier_transaction->get_transact_stage(), ==, DB::transact_Reading);
1,197✔
802
            REALM_ASSERT_3(m_notifier_transaction->get_version() + 1, ==, new_version.version);
1,197✔
803
            m_notifier_skip_version = tr.duplicate();
1,197✔
804
        }
1,197✔
805
    }
31,185✔
806

807
    if (realm.m_binding_context) {
31,185✔
808
        realm.m_binding_context->did_change({}, {});
13✔
809
    }
13✔
810
    // note: no longer safe to access `realm` or `this` after this point as
811
    // did_change() may have closed the Realm.
812
}
31,185✔
813

814
void RealmCoordinator::enable_wait_for_change()
815
{
×
816
    m_db->enable_wait_for_change();
×
817
}
×
818

819
bool RealmCoordinator::wait_for_change(std::shared_ptr<Transaction> tr)
820
{
×
821
    return m_db->wait_for_change(tr);
×
822
}
×
823

824
void RealmCoordinator::wait_for_change_release()
825
{
×
826
    m_db->wait_for_change_release();
×
827
}
×
828

829
bool RealmCoordinator::can_advance(Realm& realm)
830
{
11,808✔
831
    return realm.last_seen_transaction_version() != m_db->get_version_of_latest_snapshot();
11,808✔
832
}
11,808✔
833

834
// Thread-safety analysis doesn't reasonably handle calling functions on different
835
// instances of this type
836
void RealmCoordinator::register_notifier(std::shared_ptr<CollectionNotifier> notifier) NO_THREAD_SAFETY_ANALYSIS
837
{
5,256✔
838
    auto& self = Realm::Internal::get_coordinator(*notifier->get_realm());
5,256✔
839
    {
5,256✔
840
        util::CheckedLockGuard lock(self.m_notifier_mutex);
5,256✔
841
        notifier->set_initial_transaction(self.m_new_notifiers);
5,256✔
842
        self.m_new_notifiers.push_back(std::move(notifier));
5,256✔
843
    }
5,256✔
844
}
5,256✔
845

846
void RealmCoordinator::clean_up_dead_notifiers()
847
{
51,363✔
848
    auto swap_remove = [&](auto& container) {
102,726✔
849
        bool did_remove = false;
102,726✔
850
        for (size_t i = 0; i < container.size(); ++i) {
144,388✔
851
            if (container[i]->is_alive())
41,662✔
852
                continue;
36,475✔
853

854
            // Ensure the notifier is destroyed here even if there's lingering refs
855
            // to the async notifier elsewhere
856
            container[i]->release_data();
5,187✔
857

858
            if (container.size() > i + 1)
5,187✔
859
                container[i] = std::move(container.back());
2,661✔
860
            container.pop_back();
5,187✔
861
            --i;
5,187✔
862
            did_remove = true;
5,187✔
863
        }
5,187✔
864
        return did_remove;
102,726✔
865
    };
102,726✔
866

867
    if (swap_remove(m_notifiers) && m_notifiers.empty()) {
51,363✔
868
        m_notifier_transaction = nullptr;
2,501✔
869
        m_notifier_handover_transaction = nullptr;
2,501✔
870
        m_notifier_skip_version.reset();
2,501✔
871
    }
2,501✔
872
    swap_remove(m_new_notifiers);
51,363✔
873
}
51,363✔
874

875
void RealmCoordinator::on_commit(DB::version_type)
876
{
41,223✔
877
    if (m_notifier) {
41,223✔
878
        m_notifier->notify_others();
14,795✔
879
    }
14,795✔
880
}
41,223✔
881

882
void RealmCoordinator::on_change()
883
{
29,157✔
884
#if REALM_ENABLE_SYNC
29,157✔
885
    // Invoke realm sync if another process has notified for a change
886
    if (m_sync_session) {
29,157✔
887
        auto version = m_db->get_version_of_latest_snapshot();
5,240✔
888
        SyncSession::Internal::nonsync_transact_notify(*m_sync_session, version);
5,240✔
889
    }
5,240✔
890
#endif
29,157✔
891

892
    {
29,157✔
893
        util::CheckedUniqueLock lock(m_running_notifiers_mutex);
29,157✔
894
        run_async_notifiers();
29,157✔
895
    }
29,157✔
896

897
    util::CheckedLockGuard lock(m_realm_mutex);
29,157✔
898
    for (auto& realm : m_weak_realm_notifiers) {
38,261✔
899
        realm.notify();
38,261✔
900
    }
38,261✔
901
}
29,157✔
902

903
void RealmCoordinator::run_async_notifiers()
904
{
29,191✔
905
    util::CheckedUniqueLock lock(m_notifier_mutex);
29,191✔
906

907
    clean_up_dead_notifiers();
29,191✔
908

909
    if (m_notifiers.empty() && m_new_notifiers.empty()) {
29,191✔
910
        REALM_ASSERT(!m_notifier_skip_version);
14,621✔
911
        return;
14,621✔
912
    }
14,621✔
913

914
    if (!m_notifier_transaction) {
14,570✔
915
        REALM_ASSERT(m_notifiers.empty());
2,540✔
916
        REALM_ASSERT(!m_notifier_skip_version);
2,540✔
917
        m_notifier_transaction = m_db->start_read();
2,540✔
918
    }
2,540✔
919

920
    // We need to pick the final version to advance to while the lock is held
921
    // as otherwise if a commit is made while new notifiers are being advanced
922
    // we could end up advancing over the skip version. We create a transaction
923
    // object for it to make sure the version stays valid
924
    TransactionRef newest_transaction = m_db->start_read();
14,570✔
925
    VersionID version = newest_transaction->get_version_of_current_transaction();
14,570✔
926

927
    auto skip_version = std::move(m_notifier_skip_version);
14,570✔
928

929
    // Make a copy of the notifiers vector and then release the lock to avoid
930
    // blocking other threads trying to register or unregister notifiers while we run them
931
    decltype(m_notifiers) notifiers;
14,570✔
932
    if (version != m_notifier_transaction->get_version_of_current_transaction()) {
14,570✔
933
        // We only want to rerun the existing notifiers if the version has changed.
934
        // This is both a minor optimization and required for notification
935
        // skipping to work. The skip logic assumes that the notifier can't be
936
        // running when suppress_next() is called because it can only be called
937
        // from within a write transaction, and starting the write transaction
938
        // would have blocked until the notifier is done running. However, if we
939
        // run the notifiers at a point where the version isn't changing, that
940
        // could happen concurrently with a call to suppress_next(), and we
941
        // could unset skip_next on a callback from that zero-version run
942
        // rather than the intended one.
943
        //
944
        // Spurious wakeups can happen in a few ways: adding a new notifier,
945
        // adding a new notifier in a different process sharing this Realm file,
946
        // closing the Realm in a different process, and possibly some other cases.
947
        notifiers = m_notifiers;
6,326✔
948
    }
6,326✔
949
    else {
8,244✔
950
        REALM_ASSERT(!skip_version);
8,244✔
951
        if (m_new_notifiers.empty()) {
8,244✔
952
            // We were spuriously woken up and there isn't actually anything to do
953
            return;
5,659✔
954
        }
5,659✔
955
    }
8,244✔
956

957
    auto new_notifiers = std::move(m_new_notifiers);
8,911✔
958
    m_new_notifiers.clear();
8,911✔
959
    m_notifiers.insert(m_notifiers.end(), new_notifiers.begin(), new_notifiers.end());
8,911✔
960
    lock.unlock();
8,911✔
961

962
    // Advance all of the new notifiers to the most recent version, if any
963
    std::vector<TransactionChangeInfo> new_notifier_change_info;
8,911✔
964
    if (!new_notifiers.empty()) {
8,911✔
965
        new_notifier_change_info.reserve(new_notifiers.size());
2,586✔
966
        for (auto& notifier : new_notifiers) {
5,220✔
967
            if (notifier->version() == version)
5,220✔
968
                continue;
5,040✔
969
            new_notifier_change_info.emplace_back();
180✔
970
            notifier->add_required_change_info(new_notifier_change_info.back());
180✔
971
            transaction::parse(*newest_transaction, new_notifier_change_info.back(), notifier->version().version,
180✔
972
                               version.version);
180✔
973
        }
180✔
974
    }
2,586✔
975

976
    // If the skip version is set and we have more than one version to process,
977
    // we need to start with just the skip version so that any suppressed
978
    // callbacks can ignore the changes from it without missing changes from
979
    // later versions. If the skip version is set and there aren't any more
980
    // versions after it, we just want to process with normal processing. See
981
    // the above note about spurious wakeups for why this is required for
982
    // correctness and not just a very minor optimization.
983
    if (skip_version && skip_version->get_version_of_current_transaction() != version) {
8,911✔
984
        REALM_ASSERT(!notifiers.empty());
2✔
985
        REALM_ASSERT(version >= skip_version->get_version_of_current_transaction());
2✔
986
        TransactionChangeInfo info;
2✔
987
        for (auto& notifier : notifiers)
2✔
988
            notifier->add_required_change_info(info);
2✔
989
        transaction::advance(*m_notifier_transaction, info, skip_version->get_version_of_current_transaction());
2✔
990
        for (auto& notifier : notifiers)
2✔
991
            notifier->run();
2✔
992

993
        util::CheckedLockGuard lock(m_notifier_mutex);
2✔
994
        for (auto& notifier : notifiers)
2✔
995
            notifier->prepare_handover();
2✔
996
    }
2✔
997

998
    // Advance the non-new notifiers to the same version as we advanced the new
999
    // ones to (or the latest if there were no new ones)
1000
    TransactionChangeInfo change_info;
8,911✔
1001
    for (auto& notifier : notifiers) {
9,916✔
1002
        notifier->add_required_change_info(change_info);
9,916✔
1003
    }
9,916✔
1004
    transaction::advance(*m_notifier_transaction, change_info, version);
8,911✔
1005

1006
    {
8,911✔
1007
        // If there's multiple notifiers for a single collection, we only populate
1008
        // the data for the first one during parsing and need to copy it to the
1009
        // others. This is a reverse scan where each collection looks for the
1010
        // first collection with the same id. It is O(N^2), but typically the
1011
        // number of collections observed will be very small.
1012
        auto id = [](auto const& c) {
8,911✔
1013
            return std::tie(c.table_key, c.path, c.obj_key);
2,588✔
1014
        };
2,588✔
1015
        auto& collections = change_info.collections;
8,911✔
1016
        for (size_t i = collections.size(); i > 0; --i) {
10,905✔
1017
            for (size_t j = 0; j < i - 1; ++j) {
2,129✔
1018
                if (id(collections[i - 1]) == id(collections[j])) {
1,294✔
1019
                    collections[i - 1].changes->merge(CollectionChangeBuilder{*collections[j].changes});
1,159✔
1020
                    break;
1,159✔
1021
                }
1,159✔
1022
            }
1,294✔
1023
        }
1,994✔
1024
    }
8,911✔
1025

1026
    // Now that they're at the same version, switch the new notifiers over to
1027
    // the main Transaction used for background work rather than the temporary one
1028
    for (auto& notifier : new_notifiers) {
8,911✔
1029
        notifier->attach_to(m_notifier_transaction);
5,220✔
1030
        notifier->run();
5,220✔
1031
    }
5,220✔
1032

1033
    // Change info is now all ready, so the notifiers can now perform their
1034
    // background work
1035
    for (auto& notifier : notifiers) {
9,916✔
1036
        notifier->run();
9,916✔
1037
    }
9,916✔
1038

1039
    // Reacquire the lock while updating the fields that are actually read on
1040
    // other threads
1041
    util::CheckedLockGuard lock2(m_notifier_mutex);
8,911✔
1042
    for (auto& notifier : new_notifiers) {
8,911✔
1043
        notifier->prepare_handover();
5,220✔
1044
    }
5,220✔
1045
    for (auto& notifier : notifiers) {
9,916✔
1046
        notifier->prepare_handover();
9,916✔
1047
    }
9,916✔
1048
    clean_up_dead_notifiers();
8,911✔
1049
    if (!m_notifiers.empty())
8,911✔
1050
        m_notifier_handover_transaction = m_db->start_read(version);
8,908✔
1051
}
8,911✔
1052

1053
void RealmCoordinator::advance_to_ready(Realm& realm)
1054
{
3,197✔
1055
    // If callbacks close the Realm the last external reference may go away
1056
    // while we're in this function
1057
    auto self = shared_from_this();
3,197✔
1058
    auto tr = Realm::Internal::get_transaction_ref(realm);
3,197✔
1059
    auto current_version = tr->get_version_of_current_transaction();
3,197✔
1060

1061
    std::vector<std::shared_ptr<_impl::CollectionNotifier>> notifiers;
3,197✔
1062

1063
    // Transaction which will pin the version we're packaging for deliver to,
1064
    // to ensure it's not cleaned up between when we release the mutex and when
1065
    // we actually advance (which is not done while holding a lock).
1066
    std::shared_ptr<Transaction> handover_version_tr;
3,197✔
1067
    {
3,197✔
1068
        util::CheckedLockGuard lock(m_notifier_mutex);
3,197✔
1069

1070
        // If there are any new notifiers for this Realm then by definition they
1071
        // haven't run yet and aren't ready
1072
        for (auto& notifier : m_new_notifiers) {
3,197✔
1073
            if (notifier->is_for_realm(realm))
×
1074
                return;
×
1075
        }
×
1076

1077
        for (auto& notifier : m_notifiers) {
4,479✔
1078
            if (!notifier->is_for_realm(realm))
4,479✔
1079
                continue;
23✔
1080
            // If the notifier hasn't run it isn't ready and we should do nothing
1081
            if (!notifier->has_run())
4,456✔
1082
                return;
×
1083
            // package_for_delivery() returning false indicates that it's been
1084
            // unregistered but not yet cleaned up, so it effectively doesn't exist
1085
            if (!notifier->package_for_delivery())
4,456✔
1086
                continue;
×
1087
            notifiers.push_back(notifier);
4,456✔
1088
        }
4,456✔
1089

1090
        handover_version_tr = m_notifier_handover_transaction;
3,197✔
1091
    }
3,197✔
1092

1093
    if (notifiers.empty()) {
3,197✔
1094
        // If we have no notifiers for this Realm, just advance to latest
1095
        return transaction::advance(tr, realm.m_binding_context.get(), {});
478✔
1096
    }
478✔
1097

1098
    // If we have notifiers but no transaction, then they've never run before.
1099
    if (!handover_version_tr)
2,719✔
1100
        return;
×
1101

1102
    auto notifier_version = handover_version_tr->get_version_of_current_transaction();
2,719✔
1103
    // If the most recent write was performed via the Realm instance being
1104
    // advanced, the notifiers can be at an older version than the Realm.
1105
    // This means that there's no advancing to do
1106
    if (notifier_version < current_version)
2,719✔
1107
        return;
×
1108

1109
    // We can have notifications for the current version if it's the initial
1110
    // notification for a newly added callback or if the write was performed
1111
    // on this Realm instance. There might also be a newer version but we ignore
1112
    // it if so.
1113
    if (notifier_version == current_version) {
2,719✔
1114
        if (realm.m_binding_context)
3✔
1115
            realm.m_binding_context->will_send_notifications();
×
1116
        if (realm.is_closed())
3✔
1117
            return;
×
1118
        for (auto& notifier : notifiers)
3✔
1119
            notifier->after_advance();
3✔
1120
        if (realm.is_closed())
3✔
1121
            return;
×
1122
        if (realm.m_binding_context)
3✔
1123
            realm.m_binding_context->did_send_notifications();
×
1124
        return;
3✔
1125
    }
3✔
1126

1127
    // We have notifiers for a newer version, so advance to that
1128
    transaction::advance(tr, realm.m_binding_context.get(),
2,716✔
1129
                         _impl::NotifierPackage(std::move(notifiers), handover_version_tr));
2,716✔
1130
}
2,716✔
1131

1132
std::vector<std::shared_ptr<_impl::CollectionNotifier>> RealmCoordinator::notifiers_for_realm(Realm& realm)
1133
{
28,159✔
1134
    auto pred = [&](auto& notifier) {
28,159✔
1135
        return notifier->is_for_realm(realm);
8,315✔
1136
    };
8,315✔
1137
    std::vector<std::shared_ptr<_impl::CollectionNotifier>> ret;
28,159✔
1138
    std::copy_if(m_new_notifiers.begin(), m_new_notifiers.end(), std::back_inserter(ret), pred);
28,159✔
1139
    std::copy_if(m_notifiers.begin(), m_notifiers.end(), std::back_inserter(ret), pred);
28,159✔
1140
    return ret;
28,159✔
1141
}
28,159✔
1142

1143
bool RealmCoordinator::advance_to_latest(Realm& realm)
1144
{
3,573✔
1145
    // If callbacks close the Realm the last external reference may go away
1146
    // while we're in this function
1147
    auto self = shared_from_this();
3,573✔
1148
    auto tr = Realm::Internal::get_transaction_ref(realm);
3,573✔
1149

1150
    NotifierVector notifiers;
3,573✔
1151
    {
3,573✔
1152
        util::CheckedUniqueLock lock(m_notifier_mutex);
3,573✔
1153
        notifiers = notifiers_for_realm(realm);
3,573✔
1154
    }
3,573✔
1155
    auto pin_tr = package_notifiers(notifiers, m_db->get_version_of_latest_snapshot());
3,573✔
1156

1157
    auto prev_version = tr->get_version_of_current_transaction();
3,573✔
1158
    transaction::advance(tr, realm.m_binding_context.get(), _impl::NotifierPackage(std::move(notifiers), pin_tr));
3,573✔
1159
    return !realm.is_closed() && prev_version != tr->get_version_of_current_transaction();
3,573✔
1160
}
3,573✔
1161

1162
void RealmCoordinator::promote_to_write(Realm& realm)
1163
{
24,586✔
1164
    REALM_ASSERT(!realm.is_in_transaction());
24,586✔
1165
    // If callbacks close the Realm the last external reference may go away
1166
    // while we're in this function
1167
    auto self = shared_from_this();
24,586✔
1168

1169
    util::CheckedUniqueLock lock(m_notifier_mutex);
24,586✔
1170
    auto notifiers = notifiers_for_realm(realm);
24,586✔
1171
    lock.unlock();
24,586✔
1172

1173
    transaction::begin(Realm::Internal::get_transaction_ref(realm), realm.m_binding_context.get(),
24,586✔
1174
                       {std::move(notifiers), this});
24,586✔
1175
}
24,586✔
1176

1177
void RealmCoordinator::process_available_async(Realm& realm)
1178
{
24,128✔
1179
    REALM_ASSERT(!realm.is_in_transaction());
24,128✔
1180
    // If callbacks close the Realm the last external reference may go away
1181
    // while we're in this function
1182
    auto self = shared_from_this();
24,128✔
1183

1184
    auto current_version = realm.current_transaction_version();
24,128✔
1185
    std::vector<std::shared_ptr<_impl::CollectionNotifier>> notifiers;
24,128✔
1186

1187
    {
24,128✔
1188
        util::CheckedLockGuard lock(m_notifier_mutex);
24,128✔
1189
        // No handover transaction means there can't be anything waiting to deliver
1190
        if (!m_notifier_handover_transaction)
24,128✔
1191
            return;
20,475✔
1192
        // If we have a read transaction, it needs to be an exact match in version
1193
        // to the notifications as we're only delivering initial notifications
1194
        // and not advancing.
1195
        if (current_version &&
3,653✔
1196
            current_version != m_notifier_handover_transaction->get_version_of_current_transaction())
3,653✔
1197
            return;
16✔
1198

1199
        for (auto& notifier : m_notifiers) {
7,637✔
1200
            if (!notifier->is_for_realm(realm) || !notifier->has_run() || !notifier->package_for_delivery())
7,637✔
1201
                continue;
77✔
1202
            notifiers.push_back(notifier);
7,560✔
1203
        }
7,560✔
1204
    }
3,637✔
1205
    if (notifiers.empty())
3,637✔
1206
        return;
35✔
1207

1208
    if (realm.m_binding_context)
3,602✔
1209
        realm.m_binding_context->will_send_notifications();
2✔
1210
    if (realm.is_closed()) // i.e. the Realm was closed in the callback above
3,602✔
1211
        return;
1✔
1212
    for (auto& notifier : notifiers)
3,601✔
1213
        notifier->after_advance();
7,559✔
1214
    if (realm.is_closed()) // i.e. the Realm was closed in the callback above
3,601✔
1215
        return;
1✔
1216
    if (realm.m_binding_context)
3,600✔
1217
        realm.m_binding_context->did_send_notifications();
×
1218
}
3,600✔
1219

1220
TransactionRef RealmCoordinator::package_notifiers(NotifierVector& notifiers, VersionID::version_type target_version)
1221
{
6,819✔
1222
    auto ready = [&] {
7,933✔
1223
        util::CheckedUniqueLock notifier_lock(m_notifier_mutex);
7,933✔
1224
        bool up_to_date =
7,933✔
1225
            m_notifier_handover_transaction &&
7,933✔
1226
            m_notifier_handover_transaction->get_version_of_current_transaction().version >= target_version;
7,933✔
1227
        return std::all_of(begin(notifiers), end(notifiers), [&](auto const& n) {
7,933✔
1228
            return !n->have_callbacks() || (n->has_run() && up_to_date);
5,970✔
1229
        });
5,970✔
1230
    };
7,933✔
1231

1232
    if (!ready()) {
6,819✔
1233
        util::CheckedUniqueLock lock(m_running_notifiers_mutex);
1,114✔
1234
        // The worker thread may have run the notifiers we need while we were
1235
        // waiting for the lock, so re-check
1236
        if (!ready())
1,114✔
1237
            run_async_notifiers();
34✔
1238
    }
1,114✔
1239

1240
    util::CheckedUniqueLock notifier_lock(m_notifier_mutex);
6,819✔
1241
    // If the notifiers are still out of date, that means none of them have callbacks
1242
    // so we don't want to block the calling thread to run them.
1243
    if (!m_notifier_handover_transaction ||
6,819✔
1244
        m_notifier_handover_transaction->get_version_of_current_transaction().version < target_version) {
6,819✔
1245
        notifiers.clear();
3,377✔
1246
        return nullptr;
3,377✔
1247
    }
3,377✔
1248

1249
    auto package = [&](auto& notifier) {
4,852✔
1250
        return !notifier->has_run() || !notifier->package_for_delivery();
4,852✔
1251
    };
4,852✔
1252
    notifiers.erase(std::remove_if(begin(notifiers), end(notifiers), package), end(notifiers));
3,442✔
1253
    return notifiers.empty() ? nullptr : m_notifier_handover_transaction;
3,442✔
1254
}
6,819✔
1255

1256
bool RealmCoordinator::compact()
1257
{
1✔
1258
    return m_db->compact();
1✔
1259
}
1✔
1260

1261
void RealmCoordinator::write_copy(std::string_view path, const char* key)
1262
{
13✔
1263
    m_db->write_copy(path, key);
13✔
1264
}
13✔
1265

1266
void RealmCoordinator::async_request_write_mutex(Realm& realm)
1267
{
131✔
1268
    auto tr = Realm::Internal::get_transaction_ref(realm);
131✔
1269
    m_db->async_request_write_mutex(tr, [realm = realm.shared_from_this()]() mutable {
131✔
1270
        auto& scheduler = *realm->scheduler();
123✔
1271
        scheduler.invoke([realm = std::move(realm)] {
123✔
1272
            Realm::Internal::run_writes(*realm);
123✔
1273
        });
123✔
1274
    });
123✔
1275
}
131✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc