1 /*************************************************************************
6 * [2011] - [2015] Realm Inc
9 * NOTICE: All information contained herein is, and remains
10 * the property of Realm Incorporated and its suppliers,
11 * if any. The intellectual and technical concepts contained
12 * herein are proprietary to Realm Incorporated
13 * and its suppliers and may be covered by U.S. and Foreign Patents,
14 * patents in process, and are protected by trade secret or copyright law.
15 * Dissemination of this information or reproduction of this material
16 * is strictly forbidden unless prior written permission is obtained
17 * from Realm Incorporated.
19 **************************************************************************/
24 #include <realm/impl/cont_transact_hist.hpp>
25 #include <realm/sync/instruction_replication.hpp>
26 #include <realm/sync/transform.hpp>
28 #ifndef REALM_SYNC_HISTORY_HPP
29 #define REALM_SYNC_HISTORY_HPP
34 /// SyncProgress is the progress sent by the server in the download message. The
35 /// server scans through its history in connection with every download message.
36 /// scan_server_version is the server_version of the changeset at which the
37 /// server ended the scan. scan_client_version is the client_version for this
38 /// client that was last integrated before scan_server_version.
39 /// latest_server_version is the end of the server history, and
40 /// latest_server_session_ident is the server_session_ident corresponding to
41 /// latest_sever_version. latest_client_version is the corresponding
42 /// client_version. In other words, latest_client_version is the very latest
43 /// version of a changeset originating from this client.
45 /// The client persists the entire progress. It is not very important to persist
46 /// latest_server_version, but for consistency the entire progress is persisted.
48 using version_type = HistoryEntry::version_type;
50 version_type scan_server_version = 0;
51 version_type scan_client_version = 0;
52 version_type latest_server_version = 0;
53 std::int_fast64_t latest_server_session_ident = 0;
54 version_type latest_client_version = 0;
55 int_fast64_t downloadable_bytes = 0;
59 class ClientHistoryBase :
60 public InstructionReplication {
62 using version_type = TrivialReplication::version_type;
63 using file_ident_type = HistoryEntry::file_ident_type;
64 using salt_type = std::int_fast64_t;
65 using SyncTransactCallback = void(VersionID old_version, VersionID new_version);
67 /// Get the version of the latest snapshot of the associated Realm, as well
68 /// as the file identifier pair and the synchronization progress pair as
69 /// they are stored in that snapshot.
71 /// The returned current client version is the version of the latest
72 /// snapshot of the associated SharedGroup object, and is guaranteed to be
73 /// zero for the initial empty Realm state.
75 /// The returned file identifier pair (server, client) is the one that was
76 /// last stored by set_file_ident_pair(). If no identifier pair has been
77 /// stored yet, \a client_file_ident is set to zero.
79 /// The returned SyncProgress is the one that was last stored by
80 /// set_sync_progress(), or {} if set_sync_progress() has never been called
81 /// for the associated Realm file.
82 virtual void get_status(version_type& current_client_version,
83 file_ident_type& server_file_ident,
84 file_ident_type& client_file_ident,
85 salt_type& client_file_ident_salt,
86 SyncProgress& progress) const = 0;
88 /// Stores the server assigned file identifier pair (server, client) in the
89 /// associated Realm file, such that it is available via get_status() during
90 /// future synchronization sessions. It is an error to set this identifier
91 /// pair more than once per Realm file.
93 /// \param server_file_ident The server assigned server-side file
94 /// identifier. This can be any non-zero integer strictly less than 2**64.
95 /// The server is supposed to choose a cryptic value that cannot easily be
96 /// guessed by clients (intentionally or not), and its only purpose is to
97 /// provide a higher level of fidelity during subsequent identification of
98 /// the server Realm. The server does not have to guarantee that this
99 /// identifier is unique, but in almost all cases it will be. Since the
100 /// client will also always specify the path name when referring to a server
101 /// file, the lack of a uniqueness guarantee is effectively not a problem.
103 /// \param client_file_ident The server assigned client-side file
104 /// identifier. A client-side file identifier is a non-zero positive integer
105 /// strictly less than 2**64. The server guarantees that all client-side
106 /// file identifiers generated on behalf of a particular server Realm are
107 /// unique with respect to each other. The server is free to generate
108 /// identical identifiers for two client files if they are associated with
109 /// different server Realms.
111 /// The client is required to obtain the file identifiers before engaging in
112 /// synchronization proper, and it must store the identifiers and use them
113 /// to reestablish the connection between the client file and the server
114 /// file when engaging in future synchronization sessions.
115 virtual void set_file_ident_pair(file_ident_type server_file_ident,
116 file_ident_type client_file_ident,
117 salt_type client_file_ident_salt) = 0;
119 /// Stores the SyncProgress progress in the associated Realm file in a way
120 /// that makes it available via get_status() during future synchronization
121 /// sessions. Progress is reported by the server in the DOWNLOAD message.
123 /// See struct SyncProgress for a description of \param progress.
125 /// `progress.scan_client_version` has an effect on the process by which old
126 /// history entries are discarded.
128 /// `progress.scan_client_version` The version produced on this client by
129 /// the last changeset, that was sent to, and integrated by the server at
130 /// the time `progress.scan_server_version was produced, or zero if
131 /// `progress.scan_server_version` is zero.
133 /// Since all changesets produced after `progress.scan_client_version` are
134 /// potentially needed during operational transformation of the next
135 /// changeset received from the server, the implementation of this class
136 /// must promise to retain all history entries produced after
137 /// `progress.scan_client_version`. That is, a history entry with a
138 /// changeset, that produces version V, is guaranteed to be retained as long
139 /// as V is strictly greater than `progress.scan_client_version`.
141 /// It is an error to specify a client version that is less than the
142 /// currently stored version, since there is no way to get discarded history
144 virtual void set_sync_progress(SyncProgress progress) = 0;
147 /// Get the first history entry whose changeset produced a version that
148 /// succeeds `begin_version` and, and does not succeed `end_version`, whose
149 /// changeset was not produced by integration of a changeset received from
150 /// the server, and whose changeset was not empty.
152 /// \param begin_version, end_version The range of versions to consider. If
153 /// `begin_version` is equal to `end_version`, this is the empty range. If
154 /// `begin_version` is zero, it means that everything preceding
155 /// `end_version` is to be considered, which is again the empty range if
156 /// `end_version` is also zero. Zero is a special value in that no changeset
157 /// produces that version. It is an error if `end_version` precedes
158 /// `begin_version`, or if `end_version` is zero and `begin_version` is not.
160 /// \param buffer Owner of memory referenced by entry.changeset upon return.
162 /// \return The version produced by the changeset of the located history
163 /// entry, or zero if no history entry exists matching the specified
165 virtual version_type find_history_entry_for_upload(version_type begin_version,
166 version_type end_version,
168 std::unique_ptr<char[]>& buffer) const = 0;
172 struct UploadChangeset {
173 HistoryEntry::timestamp_type timestamp;
174 version_type client_version;
175 version_type server_version;
176 ChunkedBinaryData changeset;
177 std::unique_ptr<char[]> buffer;
180 /// find_uploadable_changesets() returns a vector of history entries. The
181 /// history entries are returned in order and starts from the first available
182 /// entry. The number of entries returned is at least one, if possible, and
183 /// is size limited by a soft limit. Returned changesets produced a version
184 /// that succeeds `begin_version` and, and does not succeed `end_version`.
185 /// Returned changesets are also locally produced and non-empty.
186 virtual std::vector<UploadChangeset> find_uploadable_changesets(version_type begin_version,
187 version_type end_version) const = 0;
189 using RemoteChangeset = Transformer::RemoteChangeset;
191 // FIXME: Apparently, this feature is expected by object store, but why?
192 // What is it ultimately used for? (@tgoyne)
193 class SyncTransactReporter {
195 virtual void report_sync_transact(VersionID old_version, VersionID new_version) = 0;
197 ~SyncTransactReporter() {}
200 /// \brief Integrate a sequence of remote changesets using a single Realm
203 /// Each changeset will be transformed as if by a call to
204 /// Transformer::transform_remote_changeset(), and then applied to the
205 /// associated Realm.
207 /// As a final step, each changeset will be added to the local history (list
208 /// of applied changesets).
210 /// \param progress is the SyncProgress received in the download message.
211 /// Progress will be persisted along with the changesets.
213 /// \param num_changesets The number of passed changesets. Must be non-zero.
215 /// \param callback An optional callback which will be called with the
216 /// version immediately processing the sync transaction and that of the sync
219 /// \return The new local version produced by the application of the
220 /// transformed changeset.
221 virtual version_type integrate_remote_changesets(SyncProgress progress,
222 const RemoteChangeset* changesets,
223 std::size_t num_changesets,
224 util::Logger* replay_logger,
225 SyncTransactReporter* = nullptr) = 0;
228 ClientHistoryBase(const std::string& realm_path);
233 class ClientHistory : public ClientHistoryBase {
235 class ChangesetCooker;
238 /// Get the persisted upload/download progress in bytes.
239 virtual void get_upload_download_bytes(uint_fast64_t& downloaded_bytes,
240 uint_fast64_t& downloadable_bytes,
241 uint_fast64_t& uploaded_bytes,
242 uint_fast64_t& uploadable_bytes,
243 uint_fast64_t& snapshot_version) = 0;
245 /// See set_cooked_progress().
246 struct CookedProgress {
247 std::int_fast64_t changeset_index = 0;
248 std::int_fast64_t intrachangeset_progress = 0;
251 /// Returns the persisted progress that was last stored by
252 /// set_cooked_progress().
254 /// Initially, until explicitly modified, both
255 /// `CookedProgress::changeset_index` and
256 /// `CookedProgress::intrachangeset_progress` are zero.
257 virtual CookedProgress get_cooked_progress() const = 0;
259 /// Persistently stores the point of progress of the consumer of cooked
262 /// As well as allowing for later retrieval, the specification of the point
263 /// of progress of the consumer of cooked changesets also has the effect of
264 /// trimming obsolete cooked changesets from the Realm file. Indeed, if this
265 /// function is never called, but cooked changesets are continually being
266 /// produced, then the Realm file will grow without bounds.
268 /// Behavior is undefined if the specified index
269 /// (CookedProgress::changeset_index) is lower than the index returned by
270 /// get_cooked_progress().
272 /// The intrachangeset progress field
273 /// (CookedProgress::intrachangeset_progress) will be faithfully persisted,
274 /// but will otherwise be treated as an opaque object by the history
276 virtual void set_cooked_progress(CookedProgress) = 0;
278 /// Get the number of cooked changesets so far produced for this Realm. This
279 /// is the number of cooked changesets that are currently in the Realm file
280 /// plus the number of cooked changesets that have been trimmed off so far.
281 virtual std::int_fast64_t get_num_cooked_changesets() const = 0;
283 /// Fetch the cooked changeset at the specified index.
285 /// Cooked changesets are made available in the order they are produced by
286 /// the changeset cooker (ChangesetCooker).
288 /// Behaviour is undefined if the specified index is less than the index
289 /// (CookedProgress::changeset_index) returned by get_cooked_progress(), or
290 /// if it is greater than, or equal to the toal number of cooked changesets
291 /// (as returned by get_num_cooked_changesets()).
293 /// The callee must append the bytes of the located cooked changeset to the
294 /// specified buffer, which does not have to be empty initially.
295 virtual void get_cooked_changeset(std::int_fast64_t index,
296 util::AppendBuffer<char>&) const = 0;
299 ClientHistory(const std::string& realm_path);
303 /// \brief Abstract interface for changeset cookers.
305 /// Note, it is completely up to the application to decide what a cooked
306 /// changeset is. History objects (instances of ClientHistory) are required to
307 /// treat cooked changesets as opaque entities. For an example of a concrete
308 /// changeset cooker, see TrivialChangesetCooker which defines the cooked
309 /// changesets to be identical copies of the raw changesets.
310 class ClientHistory::ChangesetCooker {
312 /// \brief An opportunity to produce a cooked changeset.
314 /// When the implementation chooses to produce a cooked changeset, it must
315 /// write the cooked changeset to the specified buffer, and return
316 /// true. When the implementation chooses not to produce a cooked changeset,
317 /// it must return false. The implementation is allowed to write to the
318 /// buffer, and return false, and in that case, the written data will be
321 /// \param prior_state The state of the local Realm on which the specified
322 /// raw changeset is based.
324 /// \param changeset, changeset_size The raw changeset.
326 /// \param buffer The buffer to which the cooked changeset must be written.
328 /// \return True if a cooked changeset was produced. Otherwise false.
329 virtual bool cook_changeset(const Group& prior_state,
330 const char* changeset, std::size_t changeset_size,
331 util::AppendBuffer<char>& buffer) = 0;
335 class ClientHistory::Config {
339 /// Must be set to true if, and only if the created history object
340 /// represents (is owned by) the sync agent of the specified Realm file. At
341 /// most one such instance is allowed to participate in a Realm file access
342 /// session at any point in time. Ordinarily the sync agent is encapsulated
343 /// by the sync::Client class, and the history instance representing the
344 /// agent is created transparently by sync::Client (one history instance per
345 /// sync::Session object).
346 bool owner_is_sync_agent = false;
348 /// If a changeset cooker is specified, then the created history object will
349 /// allow for a cooked changeset to be produced for each changeset of remote
350 /// origin; that is, for each changeset that is integrated during the
351 /// execution of ClientHistory::integrate_remote_changesets(). If no
352 /// changeset cooker is specified, then no cooked changesets will be
353 /// produced on behalf of the created history object.
355 /// ClientHistory::integrate_remote_changesets() will pass each incoming
356 /// changeset to the cooker after operational transformation; that is, when
357 /// the chageset is ready to be applied to the local Realm state.
358 std::shared_ptr<ChangesetCooker> changeset_cooker;
361 /// \brief Create a "sync history" implementation of the realm::Replication
364 /// The intended role for such an object is as a plugin for new
365 /// realm::SharedGroup objects.
366 std::unique_ptr<ClientHistory> make_client_history(const std::string& realm_path,
367 ClientHistory::Config = {});
373 inline ClientHistoryBase::ClientHistoryBase(const std::string& realm_path):
374 InstructionReplication{realm_path} // Throws
378 inline ClientHistory::ClientHistory(const std::string& realm_path):
379 ClientHistoryBase{realm_path} // Throws
386 #endif // REALM_SYNC_HISTORY_HPP