added iOS source code
[wl-app.git] / iOS / Pods / Realm / include / impl / collection_notifier.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_BACKGROUND_COLLECTION_HPP
20 #define REALM_BACKGROUND_COLLECTION_HPP
21
22 #include "impl/collection_change_builder.hpp"
23
24 #include <realm/util/assert.hpp>
25 #include <realm/version_id.hpp>
26
27 #include <array>
28 #include <atomic>
29 #include <exception>
30 #include <functional>
31 #include <mutex>
32 #include <unordered_map>
33
34 namespace realm {
35 class Realm;
36 class SharedGroup;
37 class Table;
38
39 namespace _impl {
40 class RealmCoordinator;
41
42 struct ListChangeInfo {
43     size_t table_ndx;
44     size_t row_ndx;
45     size_t col_ndx;
46     CollectionChangeBuilder* changes;
47 };
48
49 struct TransactionChangeInfo {
50     std::vector<bool> table_modifications_needed;
51     std::vector<bool> table_moves_needed;
52     std::vector<ListChangeInfo> lists;
53     std::vector<CollectionChangeBuilder> tables;
54     std::vector<std::vector<size_t>> column_indices;
55     std::vector<size_t> table_indices;
56     bool track_all;
57     bool schema_changed;
58 };
59
60 class DeepChangeChecker {
61 public:
62     struct OutgoingLink {
63         size_t col_ndx;
64         bool is_list;
65     };
66     struct RelatedTable {
67         size_t table_ndx;
68         std::vector<OutgoingLink> links;
69     };
70
71     DeepChangeChecker(TransactionChangeInfo const& info, Table const& root_table,
72                       std::vector<RelatedTable> const& related_tables);
73
74     bool operator()(size_t row_ndx);
75
76     // Recursively add `table` and all tables it links to to `out`, along with
77     // information about the links from them
78     static void find_related_tables(std::vector<RelatedTable>& out, Table const& table);
79
80 private:
81     TransactionChangeInfo const& m_info;
82     Table const& m_root_table;
83     const size_t m_root_table_ndx;
84     IndexSet const* const m_root_modifications;
85     std::vector<IndexSet> m_not_modified;
86     std::vector<RelatedTable> const& m_related_tables;
87
88     struct Path {
89         size_t table;
90         size_t row;
91         size_t col;
92         bool depth_exceeded;
93     };
94     std::array<Path, 4> m_current_path;
95
96     bool check_row(Table const& table, size_t row_ndx, size_t depth = 0);
97     bool check_outgoing_links(size_t table_ndx, Table const& table,
98                               size_t row_ndx, size_t depth = 0);
99 };
100
101 // A base class for a notifier that keeps a collection up to date and/or
102 // generates detailed change notifications on a background thread. This manages
103 // most of the lifetime-management issues related to sharing an object between
104 // the worker thread and the collection on the target thread, along with the
105 // thread-safe callback collection.
106 class CollectionNotifier {
107 public:
108     CollectionNotifier(std::shared_ptr<Realm>);
109     virtual ~CollectionNotifier();
110
111     // ------------------------------------------------------------------------
112     // Public API for the collections using this to get notifications:
113
114     // Stop receiving notifications from this background worker
115     // This must be called in the destructor of the collection
116     void unregister() noexcept;
117
118     // Add a callback to be called each time the collection changes
119     // This can only be called from the target collection's thread
120     // Returns a token which can be passed to remove_callback()
121     uint64_t add_callback(CollectionChangeCallback callback);
122     // Remove a previously added token. The token is no longer valid after
123     // calling this function and must not be used again. This function can be
124     // called from any thread.
125     void remove_callback(uint64_t token);
126
127     void suppress_next_notification(uint64_t token);
128
129     // ------------------------------------------------------------------------
130     // API for RealmCoordinator to manage running things and calling callbacks
131
132     bool is_for_realm(Realm&) const noexcept;
133     Realm* get_realm() const noexcept { return m_realm.get(); }
134
135     // Get the SharedGroup version which this collection can attach to (if it's
136     // in handover mode), or can deliver to (if it's been handed over to the BG worker alredad)
137     // precondition: RealmCoordinator::m_notifier_mutex is locked
138     VersionID version() const noexcept { return m_sg_version; }
139
140     // Release references to all core types
141     // This is called on the worker thread to ensure that non-thread-safe things
142     // can be destroyed on the correct thread, even if the last reference to the
143     // CollectionNotifier is released on a different thread
144     virtual void release_data() noexcept = 0;
145
146     // Prepare to deliver the new collection and call callbacks.
147     // Returns whether or not it has anything to deliver.
148     // precondition: RealmCoordinator::m_notifier_mutex is locked
149     bool package_for_delivery();
150
151     // Deliver the new state to the target collection using the given SharedGroup
152     // precondition: RealmCoordinator::m_notifier_mutex is unlocked
153     virtual void deliver(SharedGroup&) { }
154
155     // Pass the given error to all registered callbacks, then remove them
156     // precondition: RealmCoordinator::m_notifier_mutex is unlocked
157     void deliver_error(std::exception_ptr);
158
159     // Call each of the given callbacks with the changesets prepared by package_for_delivery()
160     // precondition: RealmCoordinator::m_notifier_mutex is unlocked
161     void before_advance();
162     void after_advance();
163
164     bool is_alive() const noexcept;
165
166     // precondition: RealmCoordinator::m_notifier_mutex is locked *or* is called on worker thread
167     bool has_run() const noexcept { return m_has_run; }
168
169     // Attach the handed-over query to `sg`. Must not be already attached to a SharedGroup.
170     // precondition: RealmCoordinator::m_notifier_mutex is locked
171     void attach_to(SharedGroup& sg);
172     // Create a new query handover object and stop using the previously attached
173     // SharedGroup
174     // precondition: RealmCoordinator::m_notifier_mutex is locked
175     void detach();
176
177     // Set `info` as the new ChangeInfo that will be populated by the next
178     // transaction advance, and register all required information in it
179     // precondition: RealmCoordinator::m_notifier_mutex is locked
180     void add_required_change_info(TransactionChangeInfo& info);
181
182     // precondition: RealmCoordinator::m_notifier_mutex is unlocked
183     virtual void run() = 0;
184
185     // precondition: RealmCoordinator::m_notifier_mutex is locked
186     void prepare_handover();
187
188     template <typename T>
189     class Handle;
190
191     bool have_callbacks() const noexcept { return m_have_callbacks; }
192 protected:
193     void add_changes(CollectionChangeBuilder change);
194     void set_table(Table const& table);
195     std::unique_lock<std::mutex> lock_target();
196     SharedGroup& source_shared_group();
197
198     std::function<bool (size_t)> get_modification_checker(TransactionChangeInfo const&, Table const&);
199
200 private:
201     virtual void do_attach_to(SharedGroup&) = 0;
202     virtual void do_detach_from(SharedGroup&) = 0;
203     virtual void do_prepare_handover(SharedGroup&) = 0;
204     virtual bool do_add_required_change_info(TransactionChangeInfo&) = 0;
205     virtual bool prepare_to_deliver() { return true; }
206
207     mutable std::mutex m_realm_mutex;
208     std::shared_ptr<Realm> m_realm;
209
210     VersionID m_sg_version;
211     SharedGroup* m_sg = nullptr;
212
213     bool m_has_run = false;
214     bool m_error = false;
215     std::vector<DeepChangeChecker::RelatedTable> m_related_tables;
216
217     struct Callback {
218         CollectionChangeCallback fn;
219         CollectionChangeBuilder accumulated_changes;
220         CollectionChangeSet changes_to_deliver;
221         uint64_t token;
222         bool initial_delivered;
223         bool skip_next;
224     };
225
226     // Currently registered callbacks and a mutex which must always be held
227     // while doing anything with them or m_callback_index
228     std::mutex m_callback_mutex;
229     std::vector<Callback> m_callbacks;
230
231     // Cached value for if m_callbacks is empty, needed to avoid deadlocks in
232     // run() due to lock-order inversion between m_callback_mutex and m_target_mutex
233     // It's okay if this value is stale as at worst it'll result in us doing
234     // some extra work.
235     std::atomic<bool> m_have_callbacks = {false};
236
237     // Iteration variable for looping over callbacks
238     // remove_callback() updates this when needed
239     size_t m_callback_index = -1;
240     // The number of callbacks which were present when the notifier was packaged
241     // for delivery which are still present.
242     // Updated by packaged_for_delivery and removd_callback(), and used in
243     // for_each_callback() to avoid calling callbacks registered during delivery.
244     size_t m_callback_count = -1;
245
246     uint64_t m_next_token = 0;
247
248     template<typename Fn>
249     void for_each_callback(Fn&& fn);
250
251     std::vector<Callback>::iterator find_callback(uint64_t token);
252 };
253
254 // A smart pointer to a CollectionNotifier that unregisters the notifier when
255 // the pointer is destroyed. Movable. Copying will produce a null Handle.
256 template <typename T>
257 class CollectionNotifier::Handle : public std::shared_ptr<T> {
258 public:
259     using std::shared_ptr<T>::shared_ptr;
260
261     Handle() = default;
262     ~Handle() { reset(); }
263
264     // Copying a Handle produces a null Handle.
265     Handle(const Handle&) : Handle() { }
266     Handle& operator=(const Handle& other)
267     {
268         if (this != &other) {
269             reset();
270         }
271         return *this;
272     }
273
274     Handle(Handle&&) = default;
275     Handle& operator=(Handle&& other)
276     {
277         reset();
278         std::shared_ptr<T>::operator=(std::move(other));
279         return *this;
280     }
281
282     Handle& operator=(std::shared_ptr<T>&& other)
283     {
284         reset();
285         std::shared_ptr<T>::operator=(std::move(other));
286         return *this;
287     }
288
289     void reset()
290     {
291         if (*this) {
292             this->get()->unregister();
293             std::shared_ptr<T>::reset();
294         }
295     }
296 };
297
298 // A package of CollectionNotifiers for a single Realm instance which is passed
299 // around to the various places which need to actually trigger the notifications
300 class NotifierPackage {
301 public:
302     NotifierPackage() = default;
303     NotifierPackage(std::exception_ptr error,
304                     std::vector<std::shared_ptr<CollectionNotifier>> notifiers,
305                     RealmCoordinator* coordinator);
306
307     explicit operator bool() { return !m_notifiers.empty(); }
308
309     // Get the version which this package can deliver into, or VersionID{} if
310     // it has not yet been packaged
311     util::Optional<VersionID> version() { return m_version; }
312
313     // Package the notifiers for delivery, blocking if they aren't ready for
314     // the given version.
315     // No-op if called multiple times
316     void package_and_wait(util::Optional<VersionID::version_type> target_version);
317
318     // Send the before-change notifications
319     void before_advance();
320     // Deliver the payload associated with the contained notifiers and/or the error
321     void deliver(SharedGroup& sg);
322     // Send the after-change notifications
323     void after_advance();
324
325     void add_notifier(std::shared_ptr<CollectionNotifier> notifier);
326
327 private:
328     util::Optional<VersionID> m_version;
329     std::vector<std::shared_ptr<CollectionNotifier>> m_notifiers;
330
331     RealmCoordinator* m_coordinator = nullptr;
332     std::exception_ptr m_error;
333 };
334
335 // Find which column of the row in the table contains the given container.
336 //
337 // LinkViews and Subtables know what row of their parent they're in, but not
338 // what column, so we have to just check each one.
339 template<typename Table, typename T, typename U>
340 size_t find_container_column(Table& table, size_t row_ndx, T const& expected, int type, U (Table::*getter)(size_t, size_t))
341 {
342     for (size_t i = 0, count = table.get_column_count(); i != count; ++i) {
343         if (table.get_column_type(i) == type && (table.*getter)(i, row_ndx) == expected) {
344             return i;
345         }
346     }
347     REALM_UNREACHABLE();
348 }
349
350
351 } // namespace _impl
352 } // namespace realm
353
354 #endif /* REALM_BACKGROUND_COLLECTION_HPP */