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

realm / realm-core / 2293

02 May 2024 08:09PM UTC coverage: 90.759% (+0.01%) from 90.747%
2293

push

Evergreen

web-flow
Fix a deadlock when accessing current user from inside an App listener (#7671)

App::switch_user() emitted changes without first releasing the lock on
m_user_mutex, leading to a deadlock if anyone inside the listener tried to
acquire the mutex. The rest of the places where we emitted changes were
correct.

The newly added wrapper catches this error when building with clang.

101946 of 180246 branches covered (56.56%)

14 of 17 new or added lines in 2 files covered. (82.35%)

67 existing lines in 15 files now uncovered.

212564 of 234207 relevant lines covered (90.76%)

5790527.56 hits per line

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

74.63
/src/realm/replication.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/replication.hpp>
20

21
#include <realm/list.hpp>
22
#include <realm/path.hpp>
23
#include <iostream>
24

25
using namespace realm;
26
using namespace realm::util;
27
using LogLevel = util::Logger::Level;
28

29
const char* Replication::history_type_name(int type)
30
{
18✔
31
    switch (type) {
18✔
32
        case hist_None:
✔
33
            return "None";
×
34
        case hist_OutOfRealm:
✔
35
            return "Local out of Realm";
×
36
        case hist_InRealm:
12✔
37
            return "Local in-Realm";
12✔
38
        case hist_SyncClient:
6✔
39
            return "SyncClient";
6✔
40
        case hist_SyncServer:
✔
41
            return "SyncServer";
×
42
        default:
✔
43
            return "Unknown";
×
44
    }
18✔
45
}
18✔
46

47
void Replication::initialize(DB&)
48
{
96,486✔
49
    // Nothing needs to be done here
50
}
96,486✔
51

52
void Replication::do_initiate_transact(Group&, version_type, bool)
53
{
612,165✔
54
    char* data = m_stream.get_data();
612,165✔
55
    size_t size = m_stream.get_size();
612,165✔
56
    m_encoder.set_buffer(data, data + size);
612,165✔
57
}
612,165✔
58

59
Replication::version_type Replication::prepare_commit(version_type orig_version)
60
{
586,968✔
61
    char* data = m_stream.get_data();
586,968✔
62
    size_t size = m_encoder.write_position() - data;
586,968✔
63
    version_type new_version = prepare_changeset(data, size, orig_version); // Throws
586,968✔
64
    return new_version;
586,968✔
65
}
586,968✔
66

67
void Replication::add_class(TableKey table_key, StringData name, Table::Type type)
68
{
163,404✔
69
    if (auto logger = would_log(LogLevel::debug)) {
163,404✔
70
        if (type == Table::Type::Embedded) {
27,384✔
71
            logger->log(LogCategory::object, LogLevel::debug, "Add %1 class '%2'", type, name);
3,282✔
72
        }
3,282✔
73
        else {
24,102✔
74
            logger->log(LogCategory::object, LogLevel::debug, "Add class '%1'", name);
24,102✔
75
        }
24,102✔
76
    }
27,384✔
77
    unselect_all();
163,404✔
78
    m_encoder.insert_group_level_table(table_key); // Throws
163,404✔
79
}
163,404✔
80

81
void Replication::add_class_with_primary_key(TableKey tk, StringData name, DataType pk_type, StringData pk_name, bool,
82
                                             Table::Type table_type)
83
{
95,841✔
84
    if (auto logger = would_log(LogLevel::debug)) {
95,841✔
85
        logger->log(LogCategory::object, LogLevel::debug, "Add %1 class '%2' with primary key property '%3' of %4",
61,464✔
86
                    table_type, Group::table_name_to_class_name(name), pk_name, pk_type);
61,464✔
87
    }
61,464✔
88
    REALM_ASSERT(table_type != Table::Type::Embedded);
95,841✔
89
    unselect_all();
95,841✔
90
    m_encoder.insert_group_level_table(tk); // Throws
95,841✔
91
}
95,841✔
92

93
void Replication::erase_class(TableKey tk, StringData table_name, size_t)
94
{
1,407✔
95
    if (auto logger = would_log(LogLevel::debug)) {
1,407✔
96
        logger->log(LogCategory::object, LogLevel::debug, "Remove class '%1'",
×
97
                    Group::table_name_to_class_name(table_name));
×
98
    }
×
99
    unselect_all();
1,407✔
100
    m_encoder.erase_class(tk); // Throws
1,407✔
101
}
1,407✔
102

103

104
void Replication::insert_column(const Table* t, ColKey col_key, DataType type, StringData col_name,
105
                                Table* target_table)
106
{
521,550✔
107
    if (auto logger = would_log(LogLevel::debug)) {
521,550✔
108
        const char* collection_type = "";
248,859✔
109
        if (col_key.is_collection()) {
248,859✔
110
            if (col_key.is_list()) {
105,786✔
111
                collection_type = "list ";
38,058✔
112
            }
38,058✔
113
            else if (col_key.is_dictionary()) {
67,728✔
114
                collection_type = "dictionary ";
34,290✔
115
            }
34,290✔
116
            else {
33,438✔
117
                collection_type = "set ";
33,438✔
118
            }
33,438✔
119
        }
105,786✔
120
        if (target_table) {
248,859✔
121
            logger->log(LogCategory::object, LogLevel::debug, "On class '%1': Add property '%2' %3linking '%4'",
15,114✔
122
                        t->get_class_name(), col_name, collection_type, target_table->get_class_name());
15,114✔
123
        }
15,114✔
124
        else {
233,745✔
125
            logger->log(LogCategory::object, LogLevel::debug, "On class '%1': Add property '%2' %3of %4",
233,745✔
126
                        t->get_class_name(), col_name, collection_type, type);
233,745✔
127
        }
233,745✔
128
    }
248,859✔
129
    select_table(t);                  // Throws
521,550✔
130
    m_encoder.insert_column(col_key); // Throws
521,550✔
131
}
521,550✔
132

133
void Replication::erase_column(const Table* t, ColKey col_key)
134
{
4,200✔
135
    if (auto logger = would_log(LogLevel::debug)) {
4,200✔
136
        logger->log(LogCategory::object, LogLevel::debug, "On class '%1': Remove property '%2'", t->get_class_name(),
60✔
137
                    t->get_column_name(col_key));
60✔
138
    }
60✔
139
    select_table(t);                 // Throws
4,200✔
140
    m_encoder.erase_column(col_key); // Throws
4,200✔
141
}
4,200✔
142

143
void Replication::create_object(const Table* t, GlobalKey id)
144
{
4,296,840✔
145
    if (auto logger = would_log(LogLevel::debug)) {
4,296,840✔
146
        logger->log(LogCategory::object, LogLevel::debug, "Create object '%1'", t->get_class_name());
25,362✔
147
    }
25,362✔
148
    select_table(t);                              // Throws
4,296,840✔
149
    m_encoder.create_object(id.get_local_key(0)); // Throws
4,296,840✔
150
}
4,296,840✔
151

152
void Replication::create_object_with_primary_key(const Table* t, ObjKey key, Mixed pk)
153
{
308,094✔
154
    if (auto logger = would_log(LogLevel::debug)) {
308,094✔
155
        logger->log(LogCategory::object, LogLevel::debug, "Create object '%1' with primary key %2",
106,377✔
156
                    t->get_class_name(), pk);
106,377✔
157
    }
106,377✔
158
    select_table(t);              // Throws
308,094✔
159
    m_encoder.create_object(key); // Throws
308,094✔
160
}
308,094✔
161

162
void Replication::remove_object(const Table* t, ObjKey key)
163
{
4,925,481✔
164
    if (auto logger = would_log(LogLevel::debug)) {
4,925,481✔
165
        if (t->is_embedded()) {
7,488✔
166
            logger->log(LogCategory::object, LogLevel::debug, "Remove embedded object '%1'", t->get_class_name());
3,651✔
167
        }
3,651✔
168
        else if (t->get_primary_key_column()) {
3,837✔
169
            logger->log(LogCategory::object, LogLevel::debug, "Remove object '%1' with primary key %2",
3,660✔
170
                        t->get_class_name(), t->get_primary_key(key));
3,660✔
171
        }
3,660✔
172
        else {
177✔
173
            logger->log(LogCategory::object, LogLevel::debug, "Remove object '%1'[%2]", t->get_class_name(), key);
177✔
174
        }
177✔
175
    }
7,488✔
176
    select_table(t);              // Throws
4,925,481✔
177
    m_encoder.remove_object(key); // Throws
4,925,481✔
178
}
4,925,481✔
179

180
void Replication::select_obj(ObjKey key)
181
{
16,631,766✔
182
    if (key == m_selected_obj) {
16,631,766✔
183
        return;
2,941,377✔
184
    }
2,941,377✔
185
    if (auto logger = would_log(LogLevel::debug)) {
13,690,389✔
186
        auto class_name = m_selected_table->get_class_name();
214,962✔
187
        if (m_selected_table->get_primary_key_column()) {
214,962✔
188
            auto pk = m_selected_table->get_primary_key(key);
146,169✔
189
            logger->log(LogCategory::object, LogLevel::debug, "Mutating object '%1' with primary key %2", class_name,
146,169✔
190
                        pk);
146,169✔
191
        }
146,169✔
192
        else if (m_selected_table->is_embedded()) {
68,793✔
193
            auto obj = m_selected_table->get_object(key);
19,323✔
194
            logger->log(LogCategory::object, LogLevel::debug, "Mutating object '%1' with path '%2'", class_name,
19,323✔
195
                        obj.get_id());
19,323✔
196
        }
19,323✔
197
        else {
49,470✔
198
            logger->log(LogCategory::object, LogLevel::debug, "Mutating anonymous object '%1'[%2]", class_name, key);
49,470✔
199
        }
49,470✔
200
    }
214,962✔
201
    m_selected_obj = key;
13,690,389✔
202
}
13,690,389✔
203

204
void Replication::do_set(const Table* t, ColKey col_key, ObjKey key, _impl::Instruction variant)
205
{
16,391,883✔
206
    if (variant != _impl::Instruction::instr_SetDefault) {
16,391,883✔
207
        select_table(t); // Throws
16,391,493✔
208
        select_obj(key);
16,391,493✔
209
        m_encoder.modify_object(col_key, key); // Throws
16,391,493✔
210
    }
16,391,493✔
211
}
16,391,883✔
212

213
void Replication::set(const Table* t, ColKey col_key, ObjKey key, Mixed value, _impl::Instruction variant)
214
{
16,382,718✔
215
    do_set(t, col_key, key, variant); // Throws
16,382,718✔
216
    if (auto logger = would_log(LogLevel::trace)) {
16,382,718✔
217
        if (col_key.get_type() == col_type_Link && value.is_type(type_Link)) {
30!
218
            auto target_table = t->get_opposite_table(col_key);
×
219
            if (target_table->is_embedded()) {
×
220
                logger->log(LogCategory::object, LogLevel::trace, "   Creating embedded object '%1' in '%2'",
×
221
                            target_table->get_class_name(), t->get_column_name(col_key));
×
222
            }
×
223
            else if (target_table->get_primary_key_column()) {
×
224
                auto link = value.get<ObjKey>();
×
225
                auto pk = target_table->get_primary_key(link);
×
226
                logger->log(LogCategory::object, LogLevel::trace,
×
227
                            "   Linking object '%1' with primary key %2 from '%3'", target_table->get_class_name(),
×
228
                            pk, t->get_column_name(col_key));
×
229
            }
×
230
            else {
×
231
                logger->log(LogCategory::object, LogLevel::trace, "   Linking object '%1'[%2] from '%3'",
×
232
                            target_table->get_class_name(), key, t->get_column_name(col_key));
×
233
            }
×
234
        }
×
235
        else {
30✔
236
            logger->log(LogCategory::object, LogLevel::trace, "   Set '%1' to %2", t->get_column_name(col_key),
30✔
237
                        value.to_string(util::Logger::max_width_of_value));
30✔
238
        }
30✔
239
    }
30✔
240
}
16,382,718✔
241

242
void Replication::nullify_link(const Table* t, ColKey col_key, ObjKey key)
243
{
759✔
244
    select_table(t); // Throws
759✔
245
    select_obj(key);
759✔
246
    m_encoder.modify_object(col_key, key); // Throws
759✔
247
    if (auto logger = would_log(LogLevel::trace)) {
759✔
248
        logger->log(LogCategory::object, LogLevel::trace, "   Nullify '%1'", t->get_column_name(col_key));
×
249
    }
×
250
}
759✔
251

252
void Replication::add_int(const Table* t, ColKey col_key, ObjKey key, int_fast64_t value)
253
{
8,430✔
254
    do_set(t, col_key, key); // Throws
8,430✔
255
    if (auto logger = get_logger()) {
8,430✔
256
        logger->log(LogCategory::object, LogLevel::trace, "   Adding %1 to '%2'", value, t->get_column_name(col_key));
90✔
257
    }
90✔
258
}
8,430✔
259

260

261
Path Replication::get_prop_name(Path&& path) const
262
{
×
263
    auto col_key = path[0].get_col_key();
×
264
    auto prop_name = m_selected_table->get_column_name(col_key);
×
265
    path[0] = PathElement(prop_name);
×
266
    return std::move(path);
×
267
}
×
268

269
void Replication::log_collection_operation(const char* operation, const CollectionBase& collection, Mixed value,
270
                                           Mixed index) const
271
{
1,415,922✔
272
    auto logger = would_log(LogLevel::trace);
1,415,922✔
273
    if (REALM_LIKELY(!logger))
1,415,922✔
274
        return;
1,415,922✔
275

UNCOV
276
    auto path = collection.get_short_path();
×
UNCOV
277
    auto col_key = path[0].get_col_key();
×
UNCOV
278
    auto prop_name = m_selected_table->get_column_name(col_key);
×
UNCOV
279
    path[0] = PathElement(prop_name);
×
UNCOV
280
    std::string position;
×
281
    if (!index.is_null()) {
12✔
282
        position = util::format(" at position %1", index);
12✔
283
    }
12✔
UNCOV
284
    if (Table::is_link_type(col_key.get_type()) && value.is_type(type_Link)) {
×
285
        auto target_table = m_selected_table->get_opposite_table(col_key);
×
286
        if (target_table->is_embedded()) {
×
287
            logger->log(LogCategory::object, LogLevel::trace, "   %1 embedded object '%2' in %3%4 ", operation,
×
288
                        target_table->get_class_name(), path, position);
×
289
        }
×
290
        else if (target_table->get_primary_key_column()) {
×
291
            auto link = value.get<ObjKey>();
×
292
            auto pk = target_table->get_primary_key(link);
×
293
            logger->log(LogCategory::object, LogLevel::trace, "   %1 object '%2' with primary key %3 in %4%5",
×
294
                        operation, target_table->get_class_name(), pk, path, position);
×
295
        }
×
296
        else {
×
297
            auto link = value.get<ObjKey>();
×
298
            logger->log(LogCategory::object, LogLevel::trace, "   %1 object '%2'[%3] in %4%5", operation,
×
299
                        target_table->get_class_name(), link, path, position);
×
300
        }
×
301
    }
×
UNCOV
302
    else {
×
UNCOV
303
        logger->log(LogCategory::object, LogLevel::trace, "   %1 %2 in %3%4", operation,
×
UNCOV
304
                    value.to_string(util::Logger::max_width_of_value), path, position);
×
UNCOV
305
    }
×
UNCOV
306
}
×
307

308
void Replication::list_insert(const CollectionBase& list, size_t list_ndx, Mixed value, size_t)
309
{
1,204,842✔
310
    select_collection(list);                                     // Throws
1,204,842✔
311
    m_encoder.collection_insert(list.translate_index(list_ndx)); // Throws
1,204,842✔
312
    log_collection_operation("Insert", list, value, int64_t(list_ndx));
1,204,842✔
313
}
1,204,842✔
314

315
void Replication::list_set(const CollectionBase& list, size_t list_ndx, Mixed value)
316
{
49,230✔
317
    select_collection(list);                                  // Throws
49,230✔
318
    m_encoder.collection_set(list.translate_index(list_ndx)); // Throws
49,230✔
319
    log_collection_operation("Set", list, value, int64_t(list_ndx));
49,230✔
320
}
49,230✔
321

322
void Replication::list_erase(const CollectionBase& list, size_t link_ndx)
323
{
199,950✔
324
    select_collection(list);                                    // Throws
199,950✔
325
    m_encoder.collection_erase(list.translate_index(link_ndx)); // Throws
199,950✔
326
    if (auto logger = would_log(LogLevel::trace)) {
199,950✔
327

328
        logger->log(LogCategory::object, LogLevel::trace, "   Erase '%1' at position %2",
×
329
                    get_prop_name(list.get_short_path()), link_ndx);
×
330
    }
×
331
}
199,950✔
332

333
void Replication::list_move(const CollectionBase& list, size_t from_link_ndx, size_t to_link_ndx)
334
{
1,746✔
335
    select_collection(list);                                                                           // Throws
1,746✔
336
    m_encoder.collection_move(list.translate_index(from_link_ndx), list.translate_index(to_link_ndx)); // Throws
1,746✔
337
    if (auto logger = would_log(LogLevel::trace)) {
1,746✔
338
        logger->log(LogCategory::object, LogLevel::trace, "   Move %1 to %2 in '%3'", from_link_ndx, to_link_ndx,
×
339
                    get_prop_name(list.get_short_path()));
×
340
    }
×
341
}
1,746✔
342

343
void Replication::set_insert(const CollectionBase& set, size_t set_ndx, Mixed value)
344
{
63,309✔
345
    select_collection(set);               // Throws
63,309✔
346
    m_encoder.collection_insert(set_ndx); // Throws
63,309✔
347
    log_collection_operation("Insert", set, value, Mixed());
63,309✔
348
}
63,309✔
349

350
void Replication::set_erase(const CollectionBase& set, size_t set_ndx, Mixed value)
351
{
12,522✔
352
    select_collection(set);              // Throws
12,522✔
353
    m_encoder.collection_erase(set_ndx); // Throws
12,522✔
354
    if (auto logger = would_log(LogLevel::trace)) {
12,522✔
355
        logger->log(LogCategory::object, LogLevel::trace, "   Erase %1 from '%2'", value,
×
356
                    get_prop_name(set.get_short_path()));
×
357
    }
×
358
}
12,522✔
359

360
void Replication::set_clear(const CollectionBase& set)
361
{
2,418✔
362
    select_collection(set);                 // Throws
2,418✔
363
    m_encoder.collection_clear(set.size()); // Throws
2,418✔
364
    if (auto logger = would_log(LogLevel::trace)) {
2,418✔
365
        logger->log(LogCategory::object, LogLevel::trace, "   Clear '%1'", get_prop_name(set.get_short_path()));
×
366
    }
×
367
}
2,418✔
368

369
void Replication::do_select_table(const Table* table)
370
{
916,215✔
371
    m_encoder.select_table(table->get_key()); // Throws
916,215✔
372
    m_selected_table = table;
916,215✔
373
    m_selected_list = CollectionId();
916,215✔
374
    m_selected_obj = ObjKey();
916,215✔
375
}
916,215✔
376

377
void Replication::do_select_collection(const CollectionBase& list)
378
{
240,498✔
379
    select_table(list.get_table().unchecked_ptr());
240,498✔
380
    ColKey col_key = list.get_col_key();
240,498✔
381
    ObjKey key = list.get_owner_key();
240,498✔
382
    auto path = list.get_stable_path();
240,498✔
383

384
    select_obj(key);
240,498✔
385

386
    m_encoder.select_collection(col_key, key, path); // Throws
240,498✔
387
    m_selected_list = CollectionId(list.get_table()->get_key(), key, std::move(path));
240,498✔
388
}
240,498✔
389

390
void Replication::list_clear(const CollectionBase& list)
391
{
5,706✔
392
    select_collection(list);                 // Throws
5,706✔
393
    m_encoder.collection_clear(list.size()); // Throws
5,706✔
394
    if (auto logger = would_log(LogLevel::trace)) {
5,706✔
395
        logger->log(LogCategory::object, LogLevel::trace, "   Clear '%1'", get_prop_name(list.get_short_path()));
×
396
    }
×
397
}
5,706✔
398

399
void Replication::link_list_nullify(const Lst<ObjKey>& list, size_t link_ndx)
400
{
2,136✔
401
    select_collection(list);
2,136✔
402
    m_encoder.collection_erase(link_ndx);
2,136✔
403
    if (auto logger = would_log(LogLevel::trace)) {
2,136✔
404
        logger->log(LogCategory::object, LogLevel::trace, "   Nullify '%1' position %2",
×
405
                    m_selected_table->get_column_name(list.get_col_key()), link_ndx);
×
406
    }
×
407
}
2,136✔
408

409
void Replication::dictionary_insert(const CollectionBase& dict, size_t ndx, Mixed key, Mixed value)
410
{
89,040✔
411
    select_collection(dict);
89,040✔
412
    m_encoder.collection_insert(ndx);
89,040✔
413
    log_collection_operation("Insert", dict, value, key);
89,040✔
414
}
89,040✔
415

416
void Replication::dictionary_set(const CollectionBase& dict, size_t ndx, Mixed key, Mixed value)
417
{
9,504✔
418
    select_collection(dict);
9,504✔
419
    m_encoder.collection_set(ndx);
9,504✔
420
    log_collection_operation("Set", dict, value, key);
9,504✔
421
}
9,504✔
422

423
void Replication::dictionary_erase(const CollectionBase& dict, size_t ndx, Mixed key)
424
{
12,228✔
425
    select_collection(dict);
12,228✔
426
    m_encoder.collection_erase(ndx);
12,228✔
427
    if (auto logger = would_log(LogLevel::trace)) {
12,228✔
428
        logger->log(LogCategory::object, LogLevel::trace, "   Erase %1 from '%2'", key,
×
429
                    get_prop_name(dict.get_short_path()));
×
430
    }
×
431
}
12,228✔
432

433
void Replication::dictionary_clear(const CollectionBase& dict)
434
{
2,628✔
435
    select_collection(dict);
2,628✔
436
    m_encoder.collection_clear(dict.size());
2,628✔
437
    if (auto logger = would_log(LogLevel::trace)) {
2,628✔
438
        logger->log(LogCategory::object, LogLevel::trace, "   Clear '%1'", get_prop_name(dict.get_short_path()));
×
439
    }
×
440
}
2,628✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc