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

realm / realm-core / 2131

13 Mar 2024 04:51AM UTC coverage: 91.785% (-0.05%) from 91.833%
2131

push

Evergreen

web-flow
Merge pull request #7402 from realm/tg/obj-perf

Make Obj trivial and add a separate ObjCollectionParent type

94394 of 174600 branches covered (54.06%)

496 of 559 new or added lines in 21 files covered. (88.73%)

224 existing lines in 28 files now uncovered.

242743 of 264469 relevant lines covered (91.79%)

5639637.18 hits per line

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

90.7
/src/realm/object-store/impl/collection_notifier.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/impl/collection_notifier.hpp>
20

21
#include <realm/object-store/impl/deep_change_checker.hpp>
22
#include <realm/object-store/impl/realm_coordinator.hpp>
23
#include <realm/object-store/shared_realm.hpp>
24

25
#include <realm/db.hpp>
26
#include <realm/dictionary.hpp>
27
#include <realm/list.hpp>
28
#include <realm/set.hpp>
29
#include <realm/util/logger.hpp>
30

31
using namespace realm;
32
using namespace realm::_impl;
33

34
bool CollectionNotifier::any_related_table_was_modified(TransactionChangeInfo const& info) const noexcept
35
{
4,861✔
36
    // Check if any of the tables accessible from the root table were actually modified.
2,420✔
37
    // This includes insertions which need to be checked to catch modifications via a backlink.
2,420✔
38
    auto check_related_table = [&](auto& related_table) {
6,043✔
39
        auto it = info.tables.find(related_table.table_key);
6,043✔
40
        return it != info.tables.end() && (!it->second.modifications_empty() || !it->second.insertions_empty());
6,043✔
41
    };
6,043✔
42
    return any_of(begin(m_related_tables), end(m_related_tables), check_related_table);
4,861✔
43
}
4,861✔
44

45
util::UniqueFunction<bool(ObjKey)> CollectionNotifier::get_modification_checker(TransactionChangeInfo const& info,
46
                                                                                ConstTableRef root_table)
47
{
3,281✔
48
    // If new links were added to existing tables we need to recalculate our
1,630✔
49
    // related tables info. This'll also happen for schema changes that don't
1,630✔
50
    // matter, but making this check more precise than "any schema change at all
1,630✔
51
    // happened" would mostly just be a source of potential bugs.
1,630✔
52
    if (info.schema_changed) {
3,281✔
53
        util::CheckedLockGuard lock(m_callback_mutex);
2,006✔
54
        update_related_tables(*root_table);
2,006✔
55
    }
2,006✔
56

1,630✔
57
    if (!any_related_table_was_modified(info)) {
3,281✔
58
        return [](ObjKey) {
430✔
59
            return false;
430✔
60
        };
430✔
61
    }
82✔
62

1,589✔
63
    // If the table in question has no outgoing links it will be the only entry in `m_related_tables`.
1,589✔
64
    // In this case we do not need a `DeepChangeChecker` and check the modifications using the
1,589✔
65
    // `ObjectChangeSet` within the `TransactionChangeInfo` for this table directly.
1,589✔
66
    if (m_related_tables.size() == 1 && !all_callbacks_filtered()) {
3,199✔
67
        auto root_table_key = m_related_tables[0].table_key;
1,729✔
68
        auto& object_change_set = info.tables.find(root_table_key)->second;
1,729✔
69
        return [&](ObjKey object_key) {
3,899✔
70
            return object_change_set.modifications_contains(object_key, {});
3,899✔
71
        };
3,899✔
72
    }
1,729✔
73

735✔
74
    if (all_callbacks_filtered()) {
1,470✔
75
        return CollectionKeyPathChangeChecker(info, *root_table, m_related_tables, m_key_path_array,
136✔
76
                                              m_all_callbacks_filtered);
136✔
77
    }
136✔
78
    else if (any_callbacks_filtered()) {
1,334✔
79
        // In case we have some callbacks, we need to combine the unfiltered `DeepChangeChecker` with
15✔
80
        // the filtered `CollectionKeyPathChangeChecker` to make sure we send all expected notifications.
15✔
81
        CollectionKeyPathChangeChecker key_path_checker(info, *root_table, m_related_tables, m_key_path_array,
30✔
82
                                                        m_all_callbacks_filtered);
30✔
83
        DeepChangeChecker deep_change_checker(info, *root_table, m_related_tables, m_key_path_array,
30✔
84
                                              m_all_callbacks_filtered);
30✔
85
        return [key_path_checker = std::move(key_path_checker),
30✔
86
                deep_change_checker = std::move(deep_change_checker)](ObjKey object_key) mutable {
228✔
87
            return key_path_checker(object_key) || deep_change_checker(object_key);
228✔
88
        };
228✔
89
    }
30✔
90

652✔
91
    return DeepChangeChecker(info, *root_table, m_related_tables, m_key_path_array, m_all_callbacks_filtered);
1,304✔
92
}
1,304✔
93

94
util::UniqueFunction<std::vector<ColKey>(ObjKey)>
95
CollectionNotifier::get_object_modification_checker(TransactionChangeInfo const& info, ConstTableRef root_table)
96
{
78✔
97
    return ObjectKeyPathChangeChecker(info, *root_table, m_related_tables, m_key_path_array,
78✔
98
                                      m_all_callbacks_filtered);
78✔
99
}
78✔
100

101
void CollectionNotifier::recalculate_key_path_array()
102
{
9,567✔
103
    m_all_callbacks_filtered = true;
9,567✔
104
    m_any_callbacks_filtered = false;
9,567✔
105
    m_key_path_array.clear();
9,567✔
106
    for (const auto& callback : m_callbacks) {
9,625✔
107
        if (!callback.key_path_array) {
9,625✔
108
            m_all_callbacks_filtered = false;
9,361✔
109
        }
9,361✔
110
        else {
264✔
111
            m_any_callbacks_filtered = true;
264✔
112
            for (const auto& key_path : *callback.key_path_array) {
240✔
113
                m_key_path_array.push_back(key_path);
216✔
114
            }
216✔
115
        }
264✔
116
    }
9,625✔
117
}
9,567✔
118

119
bool CollectionNotifier::any_callbacks_filtered() const noexcept
120
{
10,906✔
121
    return m_any_callbacks_filtered;
10,906✔
122
}
10,906✔
123

124
bool CollectionNotifier::all_callbacks_filtered() const noexcept
125
{
3,277✔
126
    return m_all_callbacks_filtered;
3,277✔
127
}
3,277✔
128

129
CollectionNotifier::CollectionNotifier(std::shared_ptr<Realm> realm)
130
    : m_realm(std::move(realm))
131
    , m_transaction(Realm::Internal::get_transaction_ref(*m_realm))
132
{
10,493✔
133
    if (auto logger = m_transaction->get_logger()) {
10,493✔
134
        // We only have logging at debug and trace levels
5,247✔
135
        if (logger->would_log(util::LogCategory::notification, util::Logger::Level::debug)) {
10,493✔
136
            m_logger = logger;
6,596✔
137
        }
6,596✔
138
    }
10,493✔
139
}
10,493✔
140

141
CollectionNotifier::~CollectionNotifier()
142
{
10,493✔
143
    // Need to do this explicitly to ensure m_realm is destroyed with the mutex
5,247✔
144
    // held to avoid potential double-deletion
5,247✔
145
    unregister();
10,493✔
146
    if (m_logger) {
10,493✔
147
        m_logger->log(util::LogCategory::notification, util::Logger::Level::debug, "Notifier %1 gone", m_description);
6,596✔
148
    }
6,596✔
149
}
10,493✔
150

151
VersionID CollectionNotifier::version() const noexcept
152
{
21,357✔
153
    return m_transaction->get_version_of_current_transaction();
21,357✔
154
}
21,357✔
155

156
void CollectionNotifier::release_data() noexcept
157
{
10,493✔
158
    m_transaction = nullptr;
10,493✔
159
}
10,493✔
160

161
static bool all_have_filters(std::vector<NotificationCallback> const& callbacks) noexcept
162
{
528✔
163
    return std::all_of(callbacks.begin(), callbacks.end(), [](auto& cb) {
336✔
164
        return !cb.key_path_array;
144✔
165
    });
144✔
166
}
528✔
167

168
uint64_t CollectionNotifier::add_callback(CollectionChangeCallback callback,
169
                                          std::optional<KeyPathArray> key_path_array)
170
{
10,703✔
171
    m_realm->verify_thread();
10,703✔
172

5,352✔
173
    util::CheckedLockGuard lock(m_callback_mutex);
10,703✔
174
    // If we're adding a callback with a keypath filter or if previously all
5,352✔
175
    // callbacks had filters but this one doesn't we will need to recalculate
5,352✔
176
    // the related tables on the background thread.
5,352✔
177
    if (!key_path_array || all_have_filters(m_callbacks)) {
10,703✔
178
        m_did_modify_callbacks = true;
10,679✔
179
    }
10,679✔
180

5,352✔
181
    auto token = m_next_token++;
10,703✔
182
    m_callbacks.push_back({std::move(callback), {}, {}, std::move(key_path_array), token, false, false});
10,703✔
183

5,352✔
184
    if (m_callback_index == npos) { // Don't need to wake up if we're already sending notifications
10,703✔
185
        Realm::Internal::get_coordinator(*m_realm).wake_up_notifier_worker();
10,697✔
186
        m_have_callbacks = true;
10,697✔
187
    }
10,697✔
188
    return token;
10,703✔
189
}
10,703✔
190

191
void CollectionNotifier::remove_callback(uint64_t token)
192
{
10,703✔
193
    // the callback needs to be destroyed after releasing the lock as destroying
5,352✔
194
    // it could cause user code to be called
5,352✔
195
    NotificationCallback old;
10,703✔
196
    {
10,703✔
197
        util::CheckedLockGuard lock(m_callback_mutex);
10,703✔
198
        auto it = find_callback(token);
10,703✔
199
        if (it == end(m_callbacks)) {
10,703✔
200
            return;
×
201
        }
×
202

5,352✔
203
        size_t idx = distance(begin(m_callbacks), it);
10,703✔
204
        if (m_callback_index != npos) {
10,703✔
205
            if (m_callback_index >= idx)
24✔
206
                --m_callback_index;
22✔
207
        }
24✔
208
        --m_callback_count;
10,703✔
209

5,352✔
210
        old = std::move(*it);
10,703✔
211
        m_callbacks.erase(it);
10,703✔
212

5,352✔
213
        // If we're removing a callback with a keypath filter or the last callback
5,352✔
214
        // without a keypath filter we will need to recalcuate the related tables
5,352✔
215
        // on next run.
5,352✔
216
        if (!old.key_path_array || all_have_filters(m_callbacks)) {
10,703✔
217
            m_did_modify_callbacks = true;
10,679✔
218
        }
10,679✔
219

5,352✔
220
        m_have_callbacks = !m_callbacks.empty();
10,703✔
221
    }
10,703✔
222
}
10,703✔
223

224
void CollectionNotifier::suppress_next_notification(uint64_t token)
225
{
58✔
226
    {
58✔
227
        std::lock_guard<std::mutex> lock(m_realm_mutex);
58✔
228
        REALM_ASSERT(m_realm);
58✔
229
        m_realm->verify_thread();
58✔
230
        if (!m_realm->is_in_transaction()) {
58✔
231
            throw WrongTransactionState("Suppressing the notification from a write transaction must be done from "
2✔
232
                                        "inside the write transaction.");
2✔
233
        }
2✔
234
    }
56✔
235

28✔
236
    util::CheckedLockGuard lock(m_callback_mutex);
56✔
237
    auto it = find_callback(token);
56✔
238
    if (it != end(m_callbacks)) {
56✔
239
        // We're inside a write on this collection's Realm, so the callback
27✔
240
        // should have already been called and there are no versions after
27✔
241
        // this one yet
27✔
242
        REALM_ASSERT(it->changes_to_deliver.empty());
54✔
243
        REALM_ASSERT(it->accumulated_changes.empty());
54✔
244
        it->skip_next = true;
54✔
245
    }
54✔
246
}
56✔
247

248
std::vector<NotificationCallback>::iterator CollectionNotifier::find_callback(uint64_t token)
249
{
10,757✔
250
    REALM_ASSERT(m_callbacks.size() > 0);
10,757✔
251

5,379✔
252
    auto it = std::find_if(begin(m_callbacks), end(m_callbacks), [=](const auto& c) {
11,059✔
253
        return c.token == token;
11,059✔
254
    });
11,059✔
255
    // We should only fail to find the callback if it was removed due to an error
5,379✔
256
    REALM_ASSERT(it != end(m_callbacks));
10,757✔
257
    return it;
10,757✔
258
}
10,757✔
259

260
void CollectionNotifier::unregister() noexcept
261
{
20,986✔
262
    {
20,986✔
263
        std::lock_guard lock(m_realm_mutex);
20,986✔
264
        m_realm = nullptr;
20,986✔
265
    }
20,986✔
266
    m_is_alive.store(false, std::memory_order_release);
20,986✔
267
}
20,986✔
268

269
bool CollectionNotifier::is_alive() const noexcept
270
{
85,418✔
271
    return m_is_alive.load(std::memory_order_acquire);
85,418✔
272
}
85,418✔
273

274
std::unique_lock<std::mutex> CollectionNotifier::lock_target()
275
{
17,525✔
276
    return std::unique_lock<std::mutex>{m_realm_mutex};
17,525✔
277
}
17,525✔
278

279
void CollectionNotifier::add_required_change_info(TransactionChangeInfo& info)
280
{
18,533✔
281
    if (!do_add_required_change_info(info) || m_related_tables.empty()) {
18,533✔
282
        return;
4,336✔
283
    }
4,336✔
284

8,125✔
285
    // Create an entry in the `TransactionChangeInfo` for every table in `m_related_tables`.
8,125✔
286
    info.tables.reserve(m_related_tables.size());
14,197✔
287
    for (auto& tbl : m_related_tables)
14,197✔
288
        info.tables[tbl.table_key];
17,735✔
289
}
14,197✔
290

291
void CollectionNotifier::update_related_tables(Table const& table)
292
{
9,567✔
293
    m_related_tables.clear();
9,567✔
294
    recalculate_key_path_array();
9,567✔
295
    DeepChangeChecker::find_related_tables(m_related_tables, table, m_key_path_array);
9,567✔
296
    // We deactivate the `m_did_modify_callbacks` toggle to make sure the recalculation is only done when
4,784✔
297
    // necessary.
4,784✔
298
    m_did_modify_callbacks = false;
9,567✔
299
}
9,567✔
300

301
void CollectionNotifier::prepare_handover()
302
{
28,594✔
303
    REALM_ASSERT(m_transaction);
28,594✔
304
    do_prepare_handover(*m_transaction);
28,594✔
305
    add_changes(std::move(m_change));
28,594✔
306
    m_change = {};
28,594✔
307
    REALM_ASSERT(m_change.empty());
28,594✔
308
    m_has_run = true;
28,594✔
309

15,324✔
310
    util::CheckedLockGuard lock(m_callback_mutex);
28,594✔
311
    m_run_time_point = std::chrono::steady_clock::now();
28,594✔
312
#ifdef REALM_DEBUG
28,594✔
313
    for (auto& callback : m_callbacks)
28,594✔
314
        REALM_ASSERT(!callback.skip_next);
28,594✔
315
#endif
28,594✔
316
}
28,594✔
317

318
void CollectionNotifier::before_advance()
319
{
10,859✔
320
    for_each_callback([&](auto& lock, auto& callback) {
10,848✔
321
        if (callback.changes_to_deliver.empty()) {
10,837✔
322
            return;
3,193✔
323
        }
3,193✔
324

4,244✔
325
        auto changes = callback.changes_to_deliver;
7,644✔
326
        // acquire a local reference to the callback so that removing the
4,244✔
327
        // callback from within it can't result in a dangling pointer
4,244✔
328
        auto cb = callback.fn;
7,644✔
329
        lock.unlock_unchecked();
7,644✔
330
        cb.before(changes);
7,644✔
331
    });
7,644✔
332
}
10,859✔
333

334
static void log_changeset(util::Logger* logger, const CollectionChangeSet& changes, std::string_view description,
335
                          std::chrono::microseconds elapsed)
336
{
21,833✔
337
    if (!logger) {
21,833✔
338
        return;
11,275✔
339
    }
11,275✔
340

5,279✔
341
    logger->log(util::LogCategory::notification, util::Logger::Level::debug,
10,558✔
342
                "Delivering notifications for %1 after %2 us", description, elapsed.count());
10,558✔
343
    if (!logger->would_log(util::Logger::Level::trace)) {
10,558✔
344
        return;
10,558✔
345
    }
10,558✔
NEW
346
    if (changes.empty()) {
×
NEW
347
        logger->log(util::LogCategory::notification, util::Logger::Level::trace, "   No changes");
×
NEW
348
    }
×
NEW
349
    else {
×
NEW
350
        if (changes.collection_root_was_deleted) {
×
NEW
351
            logger->log(util::LogCategory::notification, util::Logger::Level::trace, "   collection deleted");
×
NEW
352
        }
×
NEW
353
        else if (changes.collection_was_cleared) {
×
NEW
354
            logger->log(util::LogCategory::notification, util::Logger::Level::trace, "   collection cleared");
×
NEW
355
        }
×
NEW
356
        else {
×
NEW
357
            auto log = [logger](const char* change, const IndexSet& index_set) {
×
NEW
358
                if (auto cnt = index_set.count()) {
×
NEW
359
                    std::ostringstream ostr;
×
NEW
360
                    bool first = true;
×
NEW
361
                    for (auto [a, b] : index_set) {
×
NEW
362
                        if (!first)
×
NEW
363
                            ostr << ',';
×
NEW
364
                        if (b > a + 1) {
×
NEW
365
                            ostr << '[' << a << ',' << b - 1 << ']';
×
NEW
366
                        }
×
NEW
367
                        else {
×
NEW
368
                            ostr << a;
×
NEW
369
                        }
×
NEW
370
                        first = false;
×
NEW
371
                    }
×
NEW
372
                    logger->log(util::LogCategory::notification, util::Logger::Level::trace, "   %1 %2: %3", cnt,
×
NEW
373
                                change, ostr.str().c_str());
×
NEW
374
                }
×
NEW
375
            };
×
NEW
376
            log("deletions", changes.deletions);
×
NEW
377
            log("insertions", changes.insertions);
×
NEW
378
            log("modifications", changes.modifications);
×
NEW
379
        }
×
NEW
380
    }
×
NEW
381
}
×
382

383
void CollectionNotifier::after_advance()
384
{
31,270✔
385
    using namespace std::chrono;
31,270✔
386
    auto now = steady_clock::now();
31,270✔
387

16,087✔
388
    for_each_callback([&](auto& lock, auto& callback) {
32,182✔
389
        if (callback.initial_delivered && callback.changes_to_deliver.empty()) {
32,182✔
390
            return;
10,349✔
391
        }
10,349✔
392
        callback.initial_delivered = true;
21,833✔
393

11,338✔
394
        auto changes = std::move(callback.changes_to_deliver).finalize();
21,833✔
395
        callback.changes_to_deliver = {};
21,833✔
396
        // acquire a local reference to the callback so that removing the
11,338✔
397
        // callback from within it can't result in a dangling pointer
11,338✔
398
        auto cb = callback.fn;
21,833✔
399
        auto elapsed = duration_cast<microseconds>(now - m_run_time_point);
21,833✔
400
        lock.unlock_unchecked();
21,833✔
401
        log_changeset(m_logger.get(), changes, m_description, elapsed);
21,833✔
402
        cb.after(changes);
21,833✔
403
    });
21,833✔
404
}
31,270✔
405

406
bool CollectionNotifier::is_for_realm(Realm& realm) const noexcept
407
{
52,123✔
408
    std::lock_guard<std::mutex> lock(m_realm_mutex);
52,123✔
409
    return m_realm.get() == &realm;
52,123✔
410
}
52,123✔
411

412
bool CollectionNotifier::package_for_delivery()
413
{
33,302✔
414
    if (!prepare_to_deliver())
33,302✔
415
        return false;
×
416
    util::CheckedLockGuard lock(m_callback_mutex);
33,302✔
417
    for (auto& callback : m_callbacks) {
34,160✔
418
        // changes_to_deliver will normally be empty here. If it's non-empty
17,532✔
419
        // then that means package_for_delivery() was called multiple times
17,532✔
420
        // without the notification actually being delivered, which can happen
17,532✔
421
        // if the Realm was refreshed from within a notification callback.
17,532✔
422
        callback.changes_to_deliver.merge(std::move(callback.accumulated_changes));
34,160✔
423
        callback.accumulated_changes = {};
34,160✔
424
    }
34,160✔
425
    m_callback_count = m_callbacks.size();
33,302✔
426
    return true;
33,302✔
427
}
33,302✔
428

429
template <typename Fn>
430
void CollectionNotifier::for_each_callback(Fn&& fn)
431
{
42,129✔
432
    util::CheckedUniqueLock callback_lock(m_callback_mutex);
42,129✔
433
    // If this fails then package_for_delivery() was not called or returned false
21,985✔
434
    REALM_ASSERT_DEBUG(m_callback_count <= m_callbacks.size());
42,129✔
435
    for (m_callback_index = 0; m_callback_index < m_callback_count; ++m_callback_index) {
85,148✔
436
        fn(callback_lock, m_callbacks[m_callback_index]);
43,019✔
437
        if (!callback_lock.owns_lock())
43,019✔
438
            callback_lock.lock_unchecked();
29,477✔
439
    }
43,019✔
440

21,985✔
441
    m_callback_index = npos;
42,129✔
442
}
42,129✔
443

444
void CollectionNotifier::set_initial_transaction(
445
    const std::vector<std::shared_ptr<CollectionNotifier>>& other_notifiers)
446
{
10,493✔
447
    for (auto& other : other_notifiers) {
7,891✔
448
        if (version() == other->version()) {
5,288✔
449
            attach_to(other->m_transaction);
5,274✔
450
            return;
5,274✔
451
        }
5,274✔
452
    }
5,288✔
453
    attach_to(m_transaction->duplicate());
7,856✔
454
}
5,219✔
455

456

457
void CollectionNotifier::attach_to(std::shared_ptr<Transaction> tr)
458
{
20,914✔
459
    REALM_ASSERT(!m_has_run);
20,914✔
460
    // Keep the old transaction alive until the end of the function
10,458✔
461
    m_transaction.swap(tr);
20,914✔
462
    reattach();
20,914✔
463
}
20,914✔
464

465
Transaction& CollectionNotifier::source_shared_group()
466
{
5,802✔
467
    return Realm::Internal::get_transaction(*m_realm);
5,802✔
468
}
5,802✔
469

470
void CollectionNotifier::report_collection_root_is_deleted()
471
{
1,058✔
472
    if (!m_has_delivered_root_deletion_event) {
1,058✔
473
        m_change.collection_root_was_deleted = true;
794✔
474
        m_has_delivered_root_deletion_event = true;
794✔
475
    }
794✔
476
}
1,058✔
477

478
void CollectionNotifier::add_changes(CollectionChangeBuilder change)
479
{
28,594✔
480
    util::CheckedLockGuard lock(m_callback_mutex);
28,594✔
481
    for (auto& callback : m_callbacks) {
29,096✔
482
        if (callback.skip_next) {
29,096✔
483
            // Only the first commit in a batched set of transactions can be
25✔
484
            // skipped, so if we already have some changes something went wrong.
25✔
485
            REALM_ASSERT_DEBUG(callback.accumulated_changes.empty());
51✔
486
            callback.skip_next = false;
51✔
487
        }
51✔
488
        else {
29,045✔
489
            // Only copy the changeset if there's more callbacks that need it
15,550✔
490
            if (&callback == &m_callbacks.back())
29,045✔
491
                callback.accumulated_changes.merge(std::move(change));
28,451✔
492
            else
594✔
493
                callback.accumulated_changes.merge(CollectionChangeBuilder(change));
594✔
494
        }
29,045✔
495
    }
29,096✔
496
}
28,594✔
497

498
NotifierPackage::NotifierPackage(std::vector<std::shared_ptr<CollectionNotifier>> notifiers,
499
                                 RealmCoordinator* coordinator)
500
    : m_notifiers(std::move(notifiers))
501
    , m_coordinator(coordinator)
502
{
52,377✔
503
}
52,377✔
504

505
NotifierPackage::NotifierPackage(std::vector<std::shared_ptr<CollectionNotifier>> notifiers,
506
                                 std::shared_ptr<Transaction> pin_tr)
507
    : m_pin_tr(pin_tr)
508
    , m_notifiers(std::move(notifiers))
509
{
16,608✔
510
}
16,608✔
511

512
std::optional<VersionID> NotifierPackage::version() const noexcept
513
{
29,230✔
514
    if (m_pin_tr)
29,230✔
515
        return m_pin_tr->get_version_of_current_transaction();
10,718✔
516
    return std::nullopt;
18,512✔
517
}
18,512✔
518

519
void NotifierPackage::package_and_wait(VersionID::version_type target_version)
520
{
6,750✔
521
    if (!m_coordinator || !*this)
6,750✔
522
        return;
246✔
523

3,242✔
524
    m_pin_tr = m_coordinator->package_notifiers(m_notifiers, target_version);
6,504✔
525
    if (m_pin_tr && m_pin_tr->get_version_of_current_transaction().version < target_version)
6,504✔
526
        m_pin_tr.reset();
×
527
}
6,504✔
528

529
void NotifierPackage::before_advance()
530
{
69,314✔
531
    for (auto& notifier : m_notifiers)
69,314✔
532
        notifier->before_advance();
10,859✔
533
}
69,314✔
534

535
void NotifierPackage::after_advance()
536
{
71,755✔
537
    for (auto& notifier : m_notifiers)
71,755✔
538
        notifier->after_advance();
16,141✔
539
}
71,755✔
540

541
NotifierRunLogger::NotifierRunLogger(util::Logger* logger, std::string_view name, std::string_view description)
542
    : m_logger(logger)
543
    , m_name(name)
544
    , m_description(description)
545
{
24,405✔
546
    if (logger && logger->would_log(util::Logger::Level::debug)) {
24,405✔
547
        m_logger = logger;
9,850✔
548
        m_start = std::chrono::steady_clock::now();
9,850✔
549
    }
9,850✔
550
}
24,405✔
551

552
NotifierRunLogger::~NotifierRunLogger()
553
{
24,405✔
554
    using namespace std::chrono;
24,405✔
555
    if (m_logger) {
24,405✔
556
        auto now = steady_clock::now();
9,850✔
557
        m_logger->log(util::LogCategory::notification, util::Logger::Level::debug, "%1 %2 ran in %3 us", m_name,
9,850✔
558
                      m_description, duration_cast<microseconds>(now - m_start).count());
9,850✔
559
    }
9,850✔
560
}
24,405✔
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