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

realm / realm-core / github_pull_request_281750

30 Oct 2023 03:37PM UTC coverage: 90.528% (-1.0%) from 91.571%
github_pull_request_281750

Pull #6073

Evergreen

jedelbo
Log free space and history sizes when opening file
Pull Request #6073: Merge next-major

95488 of 175952 branches covered (0.0%)

8973 of 12277 new or added lines in 149 files covered. (73.09%)

622 existing lines in 51 files now uncovered.

233503 of 257934 relevant lines covered (90.53%)

6533720.56 hits per line

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

79.27
/src/realm/replication.hpp
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
#ifndef REALM_REPLICATION_HPP
20
#define REALM_REPLICATION_HPP
21

22
#include <algorithm>
23
#include <limits>
24
#include <memory>
25
#include <exception>
26
#include <string>
27

28
#include <realm/util/assert.hpp>
29
#include <realm/util/safe_int_ops.hpp>
30
#include <realm/util/buffer.hpp>
31
#include <realm/impl/cont_transact_hist.hpp>
32
#include <realm/impl/transact_log.hpp>
33

34
namespace realm {
35
namespace util {
36
class Logger;
37
}
38

39
// FIXME: Be careful about the possibility of one modification function being called by another where both do
40
// transaction logging.
41

42
/// Replication is enabled by passing an instance of an implementation of this
43
/// class to the DB constructor.
44
class Replication {
45
public:
46
    virtual ~Replication() = default;
138,624✔
47

48
    // Formerly Replication:
49
    virtual void add_class(TableKey table_key, StringData table_name, Table::Type table_type);
50
    virtual void add_class_with_primary_key(TableKey, StringData table_name, DataType pk_type, StringData pk_field,
51
                                            bool nullable, Table::Type table_type);
52
    virtual void erase_class(TableKey, StringData table_name, size_t num_tables);
53
    virtual void rename_class(TableKey table_key, StringData new_name);
54
    virtual void insert_column(const Table*, ColKey col_key, DataType type, StringData name, Table* target_table);
55
    virtual void erase_column(const Table*, ColKey col_key);
56
    virtual void rename_column(const Table*, ColKey col_key, StringData name);
57

58
    virtual void add_int(const Table*, ColKey col_key, ObjKey key, int_fast64_t value);
59
    virtual void set(const Table*, ColKey col_key, ObjKey key, Mixed value,
60
                     _impl::Instruction variant = _impl::instr_Set);
61

62
    virtual void list_set(const CollectionBase& list, size_t list_ndx, Mixed value);
63
    virtual void list_insert(const CollectionBase& list, size_t list_ndx, Mixed value, size_t prior_size);
64
    virtual void list_move(const CollectionBase&, size_t from_link_ndx, size_t to_link_ndx);
65
    virtual void list_erase(const CollectionBase&, size_t link_ndx);
66
    virtual void list_clear(const CollectionBase&);
67

68
    virtual void set_insert(const CollectionBase& set, size_t list_ndx, Mixed value);
69
    virtual void set_erase(const CollectionBase& set, size_t list_ndx, Mixed value);
70
    virtual void set_clear(const CollectionBase& set);
71

72
    virtual void dictionary_insert(const CollectionBase& dict, size_t dict_ndx, Mixed key, Mixed value);
73
    virtual void dictionary_set(const CollectionBase& dict, size_t dict_ndx, Mixed key, Mixed value);
74
    virtual void dictionary_erase(const CollectionBase& dict, size_t dict_ndx, Mixed key);
75
    virtual void dictionary_clear(const CollectionBase& dict);
76

77
    virtual void create_object(const Table*, GlobalKey);
78
    virtual void create_object_with_primary_key(const Table*, ObjKey, Mixed);
79
    virtual void remove_object(const Table*, ObjKey);
80

81
    virtual void typed_link_change(const Table*, ColKey, TableKey);
82

83
    //@{
84

85
    /// Implicit nullifications due to removal of target row. This is redundant
86
    /// information from the point of view of replication, as the removal of the
87
    /// target row will reproduce the implicit nullifications in the target
88
    /// Realm anyway. The purpose of this instruction is to allow observers
89
    /// (reactor pattern) to be explicitly notified about the implicit
90
    /// nullifications.
91

92
    virtual void nullify_link(const Table*, ColKey col_key, ObjKey key);
93
    virtual void link_list_nullify(const Lst<ObjKey>&, size_t link_ndx);
94

95

96
    // Be sure to keep this type aligned with what is actually used in DB.
97
    using version_type = _impl::History::version_type;
98
    using InputStream = util::InputStream;
99
    class TransactLogApplier;
100
    class Interrupted; // Exception
101
    class SimpleIndexTranslator;
102

103
    std::string get_database_path() const;
104

105
    /// Called during construction of the associated DB object.
106
    ///
107
    /// \param db The associated DB object.
108
    virtual void initialize(DB& db);
109

110

111
    /// \defgroup replication_transactions
112
    //@{
113

114
    /// From the point of view of the Replication class, a transaction is
115
    /// initiated when, and only when the associated Transaction object calls
116
    /// initiate_transact() and the call is successful. The associated
117
    /// Transaction object must terminate every initiated transaction either by
118
    /// calling finalize_commit() or by calling abort_transact(). It may only
119
    /// call finalize_commit(), however, after calling prepare_commit(), and
120
    /// only when prepare_commit() succeeds. If prepare_commit() fails (i.e.,
121
    /// throws) abort_transact() must still be called.
122
    ///
123
    /// The associated Transaction object is supposed to terminate a transaction
124
    /// as soon as possible, and is required to terminate it before attempting
125
    /// to initiate a new one.
126
    ///
127
    /// initiate_transact() is called by the associated Transaction object as
128
    /// part of the initiation of a transaction, and at a time where the caller
129
    /// has acquired exclusive write access to the local Realm. The Replication
130
    /// implementation is allowed to perform "precursor transactions" on the
131
    /// local Realm at this time. During the initiated transaction, the
132
    /// associated DB object must inform the Replication object of all
133
    /// modifying operations by calling set_value() and friends.
134
    ///
135
    /// FIXME: There is currently no way for implementations to perform
136
    /// precursor transactions, since a regular transaction would cause a dead
137
    /// lock when it tries to acquire a write lock. Consider giving access to
138
    /// special non-locking precursor transactions via an extra argument to this
139
    /// function.
140
    ///
141
    /// prepare_commit() serves as the first phase of a two-phase commit. This
142
    /// function is called by the associated Transaction object immediately
143
    /// before the commit operation on the local Realm. The associated
144
    /// Transaction object will then, as the second phase, either call
145
    /// finalize_commit() or abort_transact() depending on whether the commit
146
    /// operation succeeded or not. The Replication implementation is allowed to
147
    /// modify the Realm via the associated Transaction object at this time
148
    /// (important to in-Realm histories).
149
    ///
150
    /// initiate_transact() and prepare_commit() are allowed to block the
151
    /// calling thread if, for example, they need to communicate over the
152
    /// network. If a calling thread is blocked in one of these functions, it
153
    /// must be possible to interrupt the blocking operation by having another
154
    /// thread call interrupt(). The contract is as follows: When interrupt() is
155
    /// called, then any execution of initiate_transact() or prepare_commit(),
156
    /// initiated before the interruption, must complete without blocking, or
157
    /// the execution must be aborted by throwing an Interrupted exception. If
158
    /// initiate_transact() or prepare_commit() throws Interrupted, it counts as
159
    /// a failed operation.
160
    ///
161
    /// finalize_commit() is called by the associated Transaction object
162
    /// immediately after a successful commit operation on the local Realm. This
163
    /// happens at a time where modification of the Realm is no longer possible
164
    /// via the associated Transaction object. In the case of in-Realm
165
    /// histories, the changes are automatically finalized as part of the commit
166
    /// operation performed by the caller prior to the invocation of
167
    /// finalize_commit(), so in that case, finalize_commit() might not need to
168
    /// do anything.
169
    ///
170
    /// abort_transact() is called by the associated Transaction object to
171
    /// terminate a transaction without committing. That is, any transaction
172
    /// that is not terminated by finalize_commit() is terminated by
173
    /// abort_transact(). This could be due to an explicit rollback, or due to a
174
    /// failed commit attempt.
175
    ///
176
    /// Note that finalize_commit() and abort_transact() are not allowed to
177
    /// throw.
178
    ///
179
    /// \param current_version The version of the snapshot that the current
180
    /// transaction is based on.
181
    ///
182
    /// \param history_updated Pass true only when the history has already been
183
    /// updated to reflect the currently bound snapshot, such as when
184
    /// _impl::History::update_early_from_top_ref() was called during the
185
    /// transition from a read transaction to the current write transaction.
186
    ///
187
    /// \throw Interrupted Thrown by initiate_transact() and prepare_commit() if
188
    /// a blocking operation was interrupted.
189

190
    void initiate_transact(Group& group, version_type current_version, bool history_updated);
191
    /// \param current_version The version of the snapshot that the current
192
    /// transaction is based on.
193
    /// \return prepare_commit() returns the version of the new snapshot
194
    /// produced by the transaction.
195
    version_type prepare_commit(version_type current_version);
196
    void finalize_commit() noexcept;
197

198
    //@}
199

200
    /// Get the list of uncommitted changes accumulated so far in the current
201
    /// write transaction.
202
    ///
203
    /// The callee retains ownership of the referenced memory. The ownership is
204
    /// not handed over to the caller.
205
    ///
206
    /// This function may be called only during a write transaction (prior to
207
    /// initiation of commit operation). In that case, the caller may assume that the
208
    /// returned memory reference stays valid for the remainder of the transaction (up
209
    /// until initiation of the commit operation).
210
    BinaryData get_uncommitted_changes() const noexcept;
211

212
    /// CAUTION: These values are stored in Realm files, so value reassignment
213
    /// is not allowed.
214
    enum HistoryType {
215
        /// No history available. No support for either continuous transactions
216
        /// or inter-client synchronization.
217
        hist_None = 0,
218

219
        /// Out-of-Realm history supporting continuous transactions.
220
        ///
221
        /// NOTE: This history type is no longer in use. The value needs to stay
222
        /// reserved in case someone tries to open an old Realm file.
223
        hist_OutOfRealm = 1,
224

225
        /// In-Realm history supporting continuous transactions
226
        /// (make_in_realm_history()).
227
        hist_InRealm = 2,
228

229
        /// In-Realm history supporting continuous transactions and client-side
230
        /// synchronization protocol (realm::sync::ClientHistory).
231
        hist_SyncClient = 3,
232

233
        /// In-Realm history supporting continuous transactions and server-side
234
        /// synchronization protocol (realm::_impl::ServerHistory).
235
        hist_SyncServer = 4
236
    };
237

238
    static const char* history_type_name(int);
239

240
    /// Returns the type of history maintained by this Replication
241
    /// implementation, or \ref hist_None if no history is maintained by it.
242
    ///
243
    /// This type is used to ensure that all session participants agree on
244
    /// history type, and that the Realm file contains a compatible type of
245
    /// history, at the beginning of a new session.
246
    ///
247
    /// As a special case, if there is no top array (Group::m_top) at the
248
    /// beginning of a new session, then the history type is still undecided and
249
    /// all history types (as returned by get_history_type()) are threfore
250
    /// allowed for the session initiator. Note that this case only arises if
251
    /// there was no preceding session, or if no transaction was sucessfully
252
    /// committed during any of the preceding sessions. As soon as a transaction
253
    /// is successfully committed, the Realm contains at least a top array, and
254
    /// from that point on, the history type is generally fixed, although still
255
    /// subject to certain allowed changes (as mentioned below).
256
    ///
257
    /// For the sake of backwards compatibility with older Realm files that does
258
    /// not store any history type, the following rule shall apply:
259
    ///
260
    ///   - If the top array of a Realm file (Group::m_top) does not contain a
261
    ///     history type, because it is too short, it shall be understood as
262
    ///     implicitly storing the type \ref hist_None.
263
    ///
264
    /// Note: In what follows, the meaning of *preceding session* is: The last
265
    /// preceding session that modified the Realm by sucessfully committing a
266
    /// new snapshot.
267
    ///
268
    /// It shall be allowed to switch to a \ref hist_InRealm history if the
269
    /// stored history type is \ref hist_None. This can be done simply by adding
270
    /// a new history to the Realm file. This is possible because histories of
271
    /// this type a transient in nature, and need not survive from one session
272
    /// to the next.
273
    ///
274
    /// On the other hand, as soon as a history of type \ref hist_InRealm is
275
    /// added to a Realm file, that history type is binding for all subsequent
276
    /// sessions. In theory, this constraint is not necessary, and a later
277
    /// switch to \ref hist_None would be possible because of the transient
278
    /// nature of it, however, because the \ref hist_InRealm history remains in
279
    /// the Realm file, there are practical complications, and for that reason,
280
    /// such switching shall not be supported.
281
    ///
282
    /// The \ref hist_SyncClient history type can only be used if the stored
283
    /// history type is also \ref hist_SyncClient, or when there is no top array
284
    /// yet. Likewise, the \ref hist_SyncServer history type can only be used if
285
    /// the stored history type is also \ref hist_SyncServer, or when there is
286
    /// no top array yet. Additionally, when the stored history type is \ref
287
    /// hist_SyncClient or \ref hist_SyncServer, then all subsequent sessions
288
    /// must have the same type. These restrictions apply because such a history
289
    /// needs to be maintained persistently across sessions.
290
    ///
291
    /// In general, if there is no stored history type (no top array) at the
292
    /// beginning of a new session, or if the stored type disagrees with what is
293
    /// returned by get_history_type() (which is possible due to particular
294
    /// allowed changes of history type), the actual history type (as returned
295
    /// by get_history_type()) used during that session, must be stored in the
296
    /// Realm during the first successfully committed transaction in that
297
    /// session. But note that there is still no need to expand the top array to
298
    /// store the history type \ref hist_None, due to the rule mentioned above.
299
    ///
300
    /// This function must return \ref hist_None when, and only when
301
    /// get_history() returns null.
302
    virtual HistoryType get_history_type() const noexcept
303
    {
102✔
304
        return HistoryType::hist_None;
102✔
305
    }
102✔
306

307
    /// Returns the schema version of the history maintained by this Replication
308
    /// implementation, or 0 if no history is maintained by it. All session
309
    /// participants must agree on history schema version.
310
    ///
311
    /// Must return 0 if get_history_type() returns \ref hist_None.
312
    virtual int get_history_schema_version() const noexcept
313
    {
6✔
314
        return 0;
6✔
315
    }
6✔
316

317
    /// Implementation may assume that this function is only ever called with a
318
    /// stored schema version that is less than what was returned by
319
    /// get_history_schema_version().
320
    virtual bool is_upgradable_history_schema(int /* stored_schema_version */) const noexcept
321
    {
×
322
        return false;
×
323
    }
×
324

325
    /// The implementation may assume that this function is only ever called if
326
    /// is_upgradable_history_schema() was called with the same stored schema
327
    /// version, and returned true. This implies that the specified stored
328
    /// schema version is always strictly less than what was returned by
329
    /// get_history_schema_version().
330
    virtual void upgrade_history_schema(int /* stored_schema_version */) {}
×
331

332
    /// Returns an object that gives access to the history of changesets
333
    /// used by writers. All writers can share the same object as all write
334
    /// transactions are serialized.
335
    ///
336
    /// This function must return null when, and only when get_history_type()
337
    /// returns \ref hist_None.
338
    virtual _impl::History* _get_history_write()
339
    {
12✔
340
        return nullptr;
12✔
341
    }
12✔
342

343
    /// Returns an object that gives access to the history of changesets in a
344
    /// way that allows for continuous transactions to work. All readers must
345
    /// get their own exclusive object as readers are not blocking each other.
346
    /// (Group::advance_transact() in particular).
347
    ///
348
    /// This function must return null when, and only when get_history_type()
349
    /// returns \ref hist_None.
350
    virtual std::unique_ptr<_impl::History> _create_history_read()
351
    {
×
352
        return nullptr;
×
353
    }
×
354

355
    void set_logger(util::Logger* logger)
356
    {
138,687✔
357
        m_logger = logger;
138,687✔
358
    }
138,687✔
359

360
    util::Logger* get_logger() const noexcept
361
    {
44,359,824✔
362
        return m_logger;
44,359,824✔
363
    }
44,359,824✔
364

365
protected:
366
    Replication() = default;
138,624✔
367

368

369
    //@{
370

371
    /// do_initiate_transact() is called by initiate_transact(), and likewise
372
    /// for do_prepare_commit()
373
    ///
374
    /// With respect to exception safety, the Replication implementation has two
375
    /// options: It can prepare to accept the accumulated changeset in
376
    /// do_prepapre_commit() by allocating all required resources, and delay the
377
    /// actual acceptance to finalize_commit(), which requires that the final
378
    /// acceptance can be done without any risk of failure. Alternatively, the
379
    /// Replication implementation can fully accept the changeset in
380
    /// do_prepapre_commit() (allowing for failure), and then discard that
381
    /// changeset during the next invocation of do_initiate_transact() if
382
    /// `current_version` indicates that the previous transaction failed.
383

384
    virtual void do_initiate_transact(Group& group, version_type current_version, bool history_updated);
385

386
    //@}
387

388

389
    // Formerly part of TrivialReplication:
390
    virtual version_type prepare_changeset(const char*, size_t, version_type orig_version)
391
    {
6✔
392
        return orig_version + 1;
6✔
393
    }
6✔
394
    virtual void finalize_changeset() noexcept {}
556,281✔
395

396
private:
397
    struct CollectionId {
398
        TableKey table_key;
399
        ObjKey object_key;
400
        StablePath path;
401

402
        CollectionId() = default;
3,240,879✔
403
        CollectionId(const CollectionBase& list)
404
            : table_key(list.get_table()->get_key())
405
            , object_key(list.get_owner_key())
406
            , path(list.get_stable_path())
407
        {
1,607,682✔
408
        }
1,607,682✔
409
        CollectionId(TableKey t, ObjKey k, StablePath&& p)
410
            : table_key(t)
411
            , object_key(k)
412
            , path(std::move(p))
413
        {
272,316✔
414
        }
272,316✔
415
        bool operator!=(const CollectionId& other)
416
        {
1,607,709✔
417
            return object_key != other.object_key || table_key != other.table_key || path != other.path;
1,607,709✔
418
        }
1,607,709✔
419
    };
420

421
    _impl::TransactLogBufferStream m_stream;
422
    _impl::TransactLogEncoder m_encoder{m_stream};
423
    util::Logger* m_logger = nullptr;
424
    mutable const Table* m_selected_table = nullptr;
425
    mutable ObjKey m_selected_obj;
426
    mutable CollectionId m_selected_list;
427

428
    void unselect_all() noexcept;
429
    void select_table(const Table*); // unselects link list and obj
430
    void select_obj(ObjKey key);
431
    void select_collection(const CollectionBase&);
432

433
    void do_select_table(const Table*);
434
    void do_select_collection(const CollectionBase&);
435

436
    void do_set(const Table*, ColKey col_key, ObjKey key, _impl::Instruction variant = _impl::instr_Set);
437
    void log_collection_operation(const char* operation, const CollectionBase& collection, Mixed value,
438
                                  Mixed index) const;
439
    Path get_prop_name(Path&&) const;
440
    size_t transact_log_size();
441
};
442

443
class Replication::Interrupted : public std::exception {
444
public:
445
    const char* what() const noexcept override
446
    {
×
447
        return "Interrupted";
×
448
    }
×
449
};
450

451

452
// Implementation:
453

454
inline void Replication::initiate_transact(Group& group, version_type current_version, bool history_updated)
455
{
1,374,471✔
456
    if (auto hist = _get_history_write()) {
1,374,471✔
457
        hist->set_group(&group, history_updated);
1,374,432✔
458
    }
1,374,432✔
459
    do_initiate_transact(group, current_version, history_updated);
1,374,471✔
460
    unselect_all();
1,374,471✔
461
}
1,374,471✔
462

463
inline void Replication::finalize_commit() noexcept
464
{
1,344,825✔
465
    finalize_changeset();
1,344,825✔
466
}
1,344,825✔
467

468
inline BinaryData Replication::get_uncommitted_changes() const noexcept
469
{
22✔
470
    const char* data = m_stream.get_data();
22✔
471
    size_t size = m_encoder.write_position() - data;
22✔
472
    return BinaryData(data, size);
22✔
473
}
22✔
474

475
inline size_t Replication::transact_log_size()
476
{
×
477
    return m_encoder.write_position() - m_stream.get_data();
×
478
}
×
479

480

481
inline void Replication::unselect_all() noexcept
482
{
1,712,019✔
483
    m_selected_table = nullptr;
1,712,019✔
484
    m_selected_list = CollectionId();
1,712,019✔
485
}
1,712,019✔
486

487
inline void Replication::select_table(const Table* table)
488
{
27,254,727✔
489
    if (table != m_selected_table)
27,254,727✔
490
        do_select_table(table); // Throws
1,390,287✔
491
}
27,254,727✔
492

493
inline void Replication::select_collection(const CollectionBase& list)
494
{
1,607,658✔
495
    if (CollectionId(list) != m_selected_list) {
1,607,658✔
496
        do_select_collection(list); // Throws
272,316✔
497
    }
272,316✔
498
}
1,607,658✔
499

500
inline void Replication::rename_class(TableKey table_key, StringData)
UNCOV
501
{
×
502
    unselect_all();
×
503
    m_encoder.rename_class(table_key); // Throws
×
504
}
×
505

506
inline void Replication::rename_column(const Table* t, ColKey col_key, StringData)
507
{
84✔
508
    select_table(t);                  // Throws
84✔
509
    m_encoder.rename_column(col_key); // Throws
84✔
510
}
84✔
511

512
inline void Replication::typed_link_change(const Table* source_table, ColKey col, TableKey dest_table)
513
{
6,594✔
514
    select_table(source_table);
6,594✔
515
    m_encoder.typed_link_change(col, dest_table);
6,594✔
516
}
6,594✔
517

518
} // namespace realm
519

520
#endif // REALM_REPLICATION_HPP
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