• 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

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

21
#include <realm/object-store/shared_realm.hpp>
22
#include <realm/util/scope_exit.hpp>
23

24
#include <numeric>
25

26
using namespace realm;
27
using namespace realm::_impl;
28

29
// Some of the inter-thread synchronization for this class is handled externally
30
// by RealmCoordinator using the "notifier lock" which also guards registering
31
// and unregistering notifiers. This can make it somewhat difficult to tell what
32
// can safely be accessed where.
33
//
34
// The data flow is:
35
// - ResultsNotifier is created on target thread.
36
// - On background worker thread:
37
//   * do_attach_to() called with notifier lock held
38
//     - Writes to m_query
39
//   * do_add_required_change_info() called with notifier lock held
40
//     - Writes to m_info
41
//   * run() called with no locks held
42
//     - Reads m_query
43
//     - Reads m_info
44
//     - Reads m_need_to_run <-- FIXME: data race?
45
//     - Writes m_run_tv
46
//   * do_prepare_handover() called with notifier lock held
47
//     - Reads m_run_tv
48
//     - Writes m_handover_transaction
49
//     - Writes m_handover_tv
50
// - On target thread:
51
//   * prepare_to_deliver() called with notifier lock held
52
//     - Reads m_handover_transaction
53
//     - Reads m_handover_tv
54
//     - Writes m_deliver_transaction
55
//     - Writes m_deliver_handover
56
//   * get_tableview() called with no locks held
57
//     - Reads m_deliver_transaction
58
//     - Reads m_deliver_handover
59
//     - Reads m_results_were_used
60

61
ResultsNotifier::ResultsNotifier(Results& target)
62
    : ResultsNotifierBase(target.get_realm())
63
    , m_query(std::make_unique<Query>(target.get_query()))
64
    , m_descriptor_ordering(target.get_descriptor_ordering())
65
    , m_target_is_in_table_order(target.is_in_table_order())
66
{
4,048✔
67
    if (m_logger) {
4,048✔
68
        m_description = "'" + std::string(m_query->get_table()->get_class_name()) + "'";
3,272✔
69
        if (m_query->has_conditions()) {
3,272✔
70
            m_description += " where \"";
1,010✔
71
            m_description += m_query->get_description_safe() + "\"";
1,010✔
72
        }
1,010✔
73
        m_logger->log(util::LogCategory::notification, util::Logger::Level::debug, "Creating ResultsNotifier for %1",
3,272✔
74
                      m_description);
3,272✔
75
    }
3,272✔
76
    reattach();
4,048✔
77
}
4,048✔
78

79
void ResultsNotifier::release_data() noexcept
80
{
4,048✔
81
    m_query = {};
4,048✔
82
    m_run_tv = {};
4,048✔
83
    m_handover_tv = {};
4,048✔
84
    m_handover_transaction = {};
4,048✔
85
    m_delivered_tv = {};
4,048✔
86
    m_delivered_transaction = {};
4,048✔
87
    CollectionNotifier::release_data();
4,048✔
88
}
4,048✔
89

90
bool ResultsNotifier::get_tableview(TableView& out)
91
{
12,700✔
92
    if (!m_delivered_tv)
12,700✔
93
        return false;
7,288✔
94
    auto& transaction = source_shared_group();
5,412✔
95
    if (transaction.get_transact_stage() != DB::transact_Reading)
5,412✔
96
        return false;
54✔
97
    if (m_delivered_transaction->get_version_of_current_transaction() !=
5,358✔
98
        transaction.get_version_of_current_transaction())
5,358✔
99
        return false;
8✔
100

2,675✔
101
    out = std::move(*transaction.import_copy_of(*m_delivered_tv, PayloadPolicy::Move));
5,350✔
102
    m_delivered_tv.reset();
5,350✔
103
    return true;
5,350✔
104
}
5,350✔
105

106
bool ResultsNotifier::do_add_required_change_info(TransactionChangeInfo& info)
107
{
4,341✔
108
    m_info = &info;
4,341✔
109

2,160✔
110
    // When adding or removing a callback the related tables can change due to the way we calculate related tables
2,160✔
111
    // when key path filters are set hence we need to recalculate every time the callbacks are changed.
2,160✔
112
    util::CheckedLockGuard lock(m_callback_mutex);
4,341✔
113
    if (m_did_modify_callbacks) {
4,341✔
114
        update_related_tables(*m_query->get_table());
3,890✔
115
    }
3,890✔
116

2,160✔
117
    return m_query->get_table() && has_run() && have_callbacks();
4,341✔
118
}
4,341✔
119

120
void ResultsNotifier::calculate_changes()
121
{
6,697✔
122
    if (has_run() && have_callbacks()) {
6,697✔
123
        ObjKeys next_objs;
2,691✔
124
        next_objs.reserve(m_run_tv.size());
2,691✔
125
        for (size_t i = 0; i < m_run_tv.size(); ++i)
9,015✔
126
            next_objs.push_back(m_run_tv.get_key(i));
6,324✔
127

1,335✔
128
        auto table_key = m_query->get_table()->get_key();
2,691✔
129
        if (auto it = m_info->tables.find(table_key); it != m_info->tables.end()) {
2,691✔
130
            auto& changes = it->second;
2,651✔
131
            for (auto& key_val : m_previous_objs) {
5,553✔
132
                if (changes.deletions_contains(key_val)) {
5,553✔
133
                    key_val = ObjKey();
126✔
134
                }
126✔
135
            }
5,553✔
136
        }
2,651✔
137

1,335✔
138
        m_change = CollectionChangeBuilder::calculate(m_previous_objs, next_objs,
2,691✔
139
                                                      get_modification_checker(*m_info, m_query->get_table()),
2,691✔
140
                                                      m_target_is_in_table_order);
2,691✔
141

1,335✔
142
        m_previous_objs = std::move(next_objs);
2,691✔
143
    }
2,691✔
144
    else {
4,006✔
145
        size_t sz = m_run_tv.size();
4,006✔
146
        m_previous_objs.resize(sz);
4,006✔
147
        for (size_t i = 0; i < sz; ++i)
11,050✔
148
            m_previous_objs[i] = m_run_tv.get_key(i);
7,044✔
149
    }
4,006✔
150
}
6,697✔
151

152
void ResultsNotifier::run()
153
{
8,297✔
154
    NotifierRunLogger log(m_logger.get(), "ResultsNotifier", m_description);
8,297✔
155

4,138✔
156
    REALM_ASSERT(m_info || !has_run());
8,297✔
157

4,138✔
158
    // Table's been deleted, so report all objects as deleted
4,138✔
159
    if (!m_query->get_table()) {
8,297✔
160
        m_change = {};
×
161
        m_change.deletions.set(m_previous_objs.size());
×
162
        m_previous_objs.clear();
×
163
        return;
×
164
    }
×
165

4,138✔
166
    {
8,297✔
167
        auto lock = lock_target();
8,297✔
168
        // Don't run the query if the results aren't actually going to be used
4,138✔
169
        if (!get_realm() || (!have_callbacks() && !m_results_were_used))
8,297✔
170
            return;
20✔
171
    }
8,277✔
172

4,128✔
173
    auto new_versions = m_query->sync_view_if_needed();
8,277✔
174
    m_descriptor_ordering.collect_dependencies(m_query->get_table().unchecked_ptr());
8,277✔
175
    m_descriptor_ordering.get_versions(m_query->get_table()->get_parent_group(), new_versions);
8,277✔
176
    if (has_run() && new_versions == m_last_seen_version) {
8,277✔
177
        // We've run previously and none of the tables involved in the query
790✔
178
        // changed so we don't need to rerun the query, but we still need to
790✔
179
        // check each object in the results to see if it was modified
790✔
180
        if (!any_related_table_was_modified(*m_info))
1,580✔
181
            return;
1,316✔
182
        REALM_ASSERT(m_change.empty());
264✔
183
        auto checker = get_modification_checker(*m_info, m_query->get_table());
264✔
184
        for (size_t i = 0; i < m_previous_objs.size(); ++i) {
1,208✔
185
            if (checker(m_previous_objs[i])) {
944✔
186
                m_change.modifications.add(i);
194✔
187
            }
194✔
188
        }
944✔
189
        return;
264✔
190
    }
264✔
191

3,338✔
192
    m_run_tv = TableView(*m_query, size_t(-1));
6,697✔
193
    // Syncing will be done here
3,338✔
194
    m_run_tv.apply_descriptor_ordering(m_descriptor_ordering);
6,697✔
195
    m_last_seen_version = std::move(new_versions);
6,697✔
196

3,338✔
197
    calculate_changes();
6,697✔
198
}
6,697✔
199

200
void ResultsNotifier::do_prepare_handover(Transaction& sg)
201
{
8,297✔
202
    m_handover_tv.reset();
8,297✔
203
    if (m_handover_transaction)
8,297✔
204
        m_handover_transaction->advance_read(sg.get_version_of_current_transaction());
4,317✔
205

4,138✔
206
    if (m_run_tv.is_attached()) {
8,297✔
207
        REALM_ASSERT(m_run_tv.is_in_sync());
6,697✔
208
        if (!m_handover_transaction)
6,697✔
209
            m_handover_transaction = sg.duplicate();
3,980✔
210
        m_handover_tv = m_run_tv.clone_for_handover(m_handover_transaction.get(), PayloadPolicy::Move);
6,697✔
211
        m_run_tv = {};
6,697✔
212
    }
6,697✔
213
}
8,297✔
214

215
bool ResultsNotifier::prepare_to_deliver()
216
{
9,228✔
217
    auto lock = lock_target();
9,228✔
218
    auto realm = get_realm();
9,228✔
219
    if (!realm) {
9,228✔
220
        m_handover_tv.reset();
×
221
        m_delivered_tv.reset();
×
222
        return false;
×
223
    }
×
224
    if (!m_handover_tv) {
9,228✔
225
        bool transaction_is_stale =
2,565✔
226
            m_delivered_transaction &&
2,565✔
227
            (!realm->is_in_read_transaction() ||
2,503✔
228
             realm->read_transaction_version() > m_delivered_transaction->get_version_of_current_transaction());
2,439✔
229
        if (transaction_is_stale) {
2,565✔
230
            m_delivered_tv.reset();
246✔
231
            m_delivered_transaction.reset();
246✔
232
        }
246✔
233
        return true;
2,565✔
234
    }
2,565✔
235

3,322✔
236
    m_results_were_used = !m_delivered_tv;
6,663✔
237
    m_delivered_tv.reset();
6,663✔
238
    if (m_delivered_transaction)
6,663✔
239
        m_delivered_transaction->advance_read(m_handover_transaction->get_version_of_current_transaction());
2,679✔
240
    else
3,984✔
241
        m_delivered_transaction = m_handover_transaction->duplicate();
3,984✔
242
    m_delivered_tv = m_delivered_transaction->import_copy_of(*m_handover_tv, PayloadPolicy::Move);
6,663✔
243
    m_handover_tv.reset();
6,663✔
244

3,322✔
245
    return true;
6,663✔
246
}
6,663✔
247

248
void ResultsNotifier::reattach()
249
{
12,076✔
250
    if (m_query->get_table())
12,076✔
251
        m_query = transaction().import_copy_of(*m_query, PayloadPolicy::Move);
12,076✔
252
}
12,076✔
253

254
ListResultsNotifier::ListResultsNotifier(Results& target)
255
    : ResultsNotifierBase(target.get_realm())
256
    , m_list(target.get_collection())
257
{
1,812✔
258
    REALM_ASSERT(target.get_type() != PropertyType::Object);
1,812✔
259
    auto& ordering = target.get_descriptor_ordering();
1,812✔
260
    for (size_t i = 0, sz = ordering.size(); i < sz; i++) {
2,782✔
261
        auto descr = ordering[i];
970✔
262
        if (descr->get_type() == DescriptorType::Sort)
970✔
263
            m_sort_order = static_cast<const SortDescriptor*>(descr)->is_ascending(0);
928✔
264
        if (descr->get_type() == DescriptorType::Distinct)
970✔
265
            m_distinct = true;
42✔
266
    }
970✔
267
    if (m_logger) {
1,812✔
268
        auto path = m_list->get_short_path();
×
269
        auto prop_name = m_list->get_table()->get_column_name(path[0].get_col_key());
×
270
        path[0] = PathElement(prop_name);
×
NEW
271
        std::string_view sort_order = "";
×
272
        if (m_sort_order) {
×
273
            sort_order = *m_sort_order ? " sorted ascending" : " sorted descending";
×
274
        }
×
275

276
        m_description =
×
277
            util::format("%1 %2%3%4", m_list->get_collection_type(), m_list->get_obj().get_id(), path, sort_order);
×
278
        m_logger->log(util::LogCategory::notification, util::Logger::Level::debug,
×
279
                      "Creating ListResultsNotifier for %1", m_description);
×
280
    }
×
281
}
1,812✔
282

283
void ListResultsNotifier::release_data() noexcept
284
{
1,812✔
285
    m_list = {};
1,812✔
286
    CollectionNotifier::release_data();
1,812✔
287
}
1,812✔
288

289
bool ListResultsNotifier::get_list_indices(ListIndices& out)
290
{
1,496✔
291
    if (!m_delivered_indices)
1,496✔
292
        return false;
1,106✔
293
    auto& transaction = source_shared_group();
390✔
294
    if (m_delivered_transaction_version != transaction.get_version_of_current_transaction())
390✔
295
        return false;
×
296

195✔
297
    out = std::move(m_delivered_indices);
390✔
298
    m_delivered_indices = util::none;
390✔
299
    return true;
390✔
300
}
390✔
301

302
bool ListResultsNotifier::do_add_required_change_info(TransactionChangeInfo& info)
303
{
2,782✔
304
    if (!m_list->is_attached())
2,782✔
305
        return false; // origin row was deleted after the notification was added
168✔
306

1,307✔
307
    info.collections.push_back(
2,614✔
308
        {m_list->get_table()->get_key(), m_list->get_owner_key(), m_list->get_stable_path(), &m_change});
2,614✔
309

1,307✔
310
    m_info = &info;
2,614✔
311
    return true;
2,614✔
312
}
2,614✔
313

314
bool ListResultsNotifier::need_to_run()
315
{
3,754✔
316
    REALM_ASSERT(m_info || !has_run());
3,754✔
317

1,877✔
318
    // Don't run the query if the results aren't actually going to be used
1,877✔
319
    if (!is_alive() || (!have_callbacks() && !m_results_were_used))
3,754!
320
        return false;
×
321

1,877✔
322
    return !has_run() || m_list->has_changed();
3,754✔
323
}
3,754✔
324

325
void ListResultsNotifier::calculate_changes()
326
{
3,670✔
327
    // Unsorted lists can just forward the changeset directly from the
1,835✔
328
    // transaction log parsing, but sorted lists need to perform diffing
1,835✔
329
    if (has_run() && have_callbacks() && (m_sort_order || m_distinct)) {
3,670✔
330
        // Update each of the row indices in m_previous_indices to the equivalent
550✔
331
        // new index in the new list
550✔
332
        if (!m_change.insertions.empty() || !m_change.deletions.empty()) {
1,100✔
333
            for (auto& row : m_previous_indices) {
3,172✔
334
                if (m_change.deletions.contains(row))
3,172✔
335
                    row = npos;
1,762✔
336
                else
1,410✔
337
                    row = m_change.insertions.shift(m_change.deletions.unshift(row));
1,410✔
338
            }
3,172✔
339
        }
764✔
340

550✔
341
        m_change = CollectionChangeBuilder::calculate(m_previous_indices, *m_run_indices, [&](size_t index) {
2,622✔
342
            return m_change.modifications.contains(index);
2,622✔
343
        });
2,622✔
344
    }
1,100✔
345

1,835✔
346
    m_previous_indices = *m_run_indices;
3,670✔
347
}
3,670✔
348

349
void ListResultsNotifier::run()
350
{
4,426✔
351
    if (!m_list || !m_list->is_attached()) {
4,426✔
352
        // List was deleted, so report all of the rows being removed
336✔
353
        m_change = {};
672✔
354
        m_change.deletions.set(m_previous_indices.size());
672✔
355
        m_previous_indices.clear();
672✔
356
        report_collection_root_is_deleted();
672✔
357
        return;
672✔
358
    }
672✔
359

1,877✔
360
    if (!need_to_run()) {
3,754✔
361
        return;
84✔
362
    }
84✔
363

1,835✔
364
    NotifierRunLogger log(m_logger.get(), "ListResultsNotifier", m_description);
3,670✔
365

1,835✔
366
    m_run_indices = std::vector<size_t>();
3,670✔
367
    if (m_distinct)
3,670✔
368
        m_list->distinct(*m_run_indices, m_sort_order);
84✔
369
    else if (m_sort_order)
3,586✔
370
        m_list->sort(*m_run_indices, *m_sort_order);
1,902✔
371
    else {
1,684✔
372
        m_run_indices->resize(m_list->size());
1,684✔
373
        std::iota(m_run_indices->begin(), m_run_indices->end(), 0);
1,684✔
374
    }
1,684✔
375

1,835✔
376
    // Modifications to nested values in Mixed are recorded in replication as
1,835✔
377
    // StableIndex and we have to look up the actual index afterwards
1,835✔
378
    if (m_change.paths.size()) {
3,670✔
379
        if (auto coll = dynamic_cast<CollectionParent*>(m_list.get())) {
2✔
380
            for (auto& p : m_change.paths) {
2✔
381
                if (auto ndx = coll->find_index(p); ndx != realm::not_found)
2✔
382
                    m_change.modifications.add(ndx);
2✔
383
            }
2✔
384
        }
2✔
385
    }
2✔
386

1,835✔
387
    calculate_changes();
3,670✔
388
}
3,670✔
389

390
void ListResultsNotifier::do_prepare_handover(Transaction& sg)
391
{
4,426✔
392
    if (m_run_indices) {
4,426✔
393
        m_handover_indices = std::move(m_run_indices);
3,670✔
394
        m_run_indices = {};
3,670✔
395
    }
3,670✔
396
    else {
756✔
397
        m_handover_indices = {};
756✔
398
    }
756✔
399
    m_handover_transaction_version = sg.get_version_of_current_transaction();
4,426✔
400
}
4,426✔
401

402
bool ListResultsNotifier::prepare_to_deliver()
403
{
6,952✔
404
    if (!is_alive()) {
6,952✔
405
        return false;
×
406
    }
×
407
    if (!m_handover_indices)
6,952✔
408
        return true;
3,282✔
409

1,835✔
410
    m_results_were_used = !m_delivered_indices;
3,670✔
411
    m_delivered_indices = std::move(m_handover_indices);
3,670✔
412
    m_delivered_transaction_version = m_handover_transaction_version;
3,670✔
413
    m_handover_indices = {};
3,670✔
414

1,835✔
415
    return true;
3,670✔
416
}
3,670✔
417

418
void ListResultsNotifier::reattach()
419
{
3,624✔
420
    if (m_list->is_attached())
3,624✔
421
        m_list = transaction().import_copy_of(*m_list);
3,624✔
422
}
3,624✔
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