Comment and clean up the Notifiers/BackgroundCollection
This commit is contained in:
parent
4ec1090c05
commit
b920f62ca5
|
@ -32,11 +32,9 @@ BackgroundCollection::BackgroundCollection(std::shared_ptr<Realm> realm)
|
|||
|
||||
BackgroundCollection::~BackgroundCollection()
|
||||
{
|
||||
// unregister() may have been called from a different thread than we're being
|
||||
// destroyed on, so we need to synchronize access to the interesting fields
|
||||
// modified there
|
||||
std::lock_guard<std::mutex> lock(m_realm_mutex);
|
||||
m_realm = nullptr;
|
||||
// Need to do this explicitly to ensure m_realm is destroyed with the mutex
|
||||
// held to avoid potential double-deletion
|
||||
unregister();
|
||||
}
|
||||
|
||||
size_t BackgroundCollection::add_callback(CollectionChangeCallback callback)
|
||||
|
@ -102,6 +100,11 @@ bool BackgroundCollection::is_alive() const noexcept
|
|||
return m_realm != nullptr;
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> BackgroundCollection::lock_target()
|
||||
{
|
||||
return std::unique_lock<std::mutex>{m_realm_mutex};
|
||||
}
|
||||
|
||||
// Recursively add `table` and all tables it links to to `out`
|
||||
static void find_relevant_tables(std::vector<size_t>& out, Table const& table)
|
||||
{
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
|
||||
#include <mutex>
|
||||
#include <functional>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
|
||||
namespace realm {
|
||||
|
@ -34,42 +33,72 @@ class Realm;
|
|||
namespace _impl {
|
||||
struct TransactionChangeInfo;
|
||||
|
||||
// A base class for a notifier that keeps a collection up to date and/or
|
||||
// generates detailed change notifications on a background thread. This manages
|
||||
// most of the lifetime-management issues related to sharing an object between
|
||||
// the worker thread and the collection on the target thread, along with the
|
||||
// thread-safe callback collection.
|
||||
class BackgroundCollection {
|
||||
public:
|
||||
BackgroundCollection(std::shared_ptr<Realm>);
|
||||
virtual ~BackgroundCollection();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Public API for the collections using this to get notifications:
|
||||
|
||||
// Stop receiving notifications from this background worker
|
||||
// This must be called in the destructor of the collection
|
||||
void unregister() noexcept;
|
||||
|
||||
virtual void release_data() noexcept = 0;
|
||||
|
||||
// Add a callback to be called each time the collection changes
|
||||
// This can only be called from the target collection's thread
|
||||
// Returns a token which can be passed to remove_callback()
|
||||
size_t add_callback(CollectionChangeCallback callback);
|
||||
// Remove a previously added token. The token is no longer valid after
|
||||
// calling this function and must not be used again. This function can be
|
||||
// called from any thread.
|
||||
void remove_callback(size_t token);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// API for RealmCoordinator to manage running things and calling callbacks
|
||||
|
||||
Realm* get_realm() const noexcept { return m_realm.get(); }
|
||||
|
||||
// Get the SharedGroup version which this collection can attach to (if it's
|
||||
// in handover mode), or can deliver to (if it's been handed over to the BG worker alredad)
|
||||
SharedGroup::VersionID version() const noexcept { return m_sg_version; }
|
||||
|
||||
// Release references to all core types
|
||||
// This is called on the worker thread to ensure that non-thread-safe things
|
||||
// can be destroyed on the correct thread, even if the last reference to the
|
||||
// BackgroundCollection is released on a different thread
|
||||
virtual void release_data() noexcept = 0;
|
||||
|
||||
// Call each of the currently registered callbacks, if there have been any
|
||||
// changes since the last time each of those callbacks was called
|
||||
void call_callbacks();
|
||||
|
||||
bool is_alive() const noexcept;
|
||||
|
||||
Realm& get_realm() const noexcept { return *m_realm; }
|
||||
|
||||
// Attach the handed-over query to `sg`
|
||||
// Attach the handed-over query to `sg`. Must not be already attaged to a SharedGroup.
|
||||
void attach_to(SharedGroup& sg);
|
||||
// Create a new query handover object and stop using the previously attached
|
||||
// SharedGroup
|
||||
void detach();
|
||||
|
||||
void add_required_change_info(TransactionChangeInfo&);
|
||||
// Set `info` as the new ChangeInfo that will be populated by the next
|
||||
// transaction advance, and register all required information in it
|
||||
void add_required_change_info(TransactionChangeInfo& info);
|
||||
|
||||
virtual void run() { }
|
||||
void prepare_handover();
|
||||
bool deliver(SharedGroup&, std::exception_ptr);
|
||||
|
||||
// Get the version of the current handover object
|
||||
SharedGroup::VersionID version() const noexcept { return m_sg_version; }
|
||||
|
||||
protected:
|
||||
bool have_callbacks() const noexcept { return m_have_callbacks; }
|
||||
void add_changes(CollectionChangeIndices change) { m_accumulated_changes.merge(std::move(change)); }
|
||||
void set_table(Table const& table);
|
||||
std::unique_lock<std::mutex> lock_target();
|
||||
|
||||
private:
|
||||
virtual void do_attach_to(SharedGroup&) = 0;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright 2015 Realm Inc.
|
||||
// Copyright 2016 Realm Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -26,7 +26,6 @@
|
|||
using namespace realm;
|
||||
using namespace realm::_impl;
|
||||
|
||||
|
||||
ListNotifier::ListNotifier(LinkViewRef lv, std::shared_ptr<Realm> realm)
|
||||
: BackgroundCollection(std::move(realm))
|
||||
, m_prev_size(lv->size())
|
||||
|
@ -45,13 +44,12 @@ ListNotifier::ListNotifier(LinkViewRef lv, std::shared_ptr<Realm> realm)
|
|||
|
||||
set_table(lv->get_target_table());
|
||||
|
||||
auto& sg = Realm::Internal::get_shared_group(get_realm());
|
||||
auto& sg = Realm::Internal::get_shared_group(*get_realm());
|
||||
m_lv_handover = sg.export_linkview_for_handover(lv);
|
||||
}
|
||||
|
||||
void ListNotifier::release_data() noexcept
|
||||
{
|
||||
// FIXME: does this need a lock?
|
||||
m_lv.reset();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright 2015 Realm Inc.
|
||||
// Copyright 2016 Realm Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -30,12 +30,20 @@ public:
|
|||
ListNotifier(LinkViewRef lv, std::shared_ptr<Realm> realm);
|
||||
|
||||
private:
|
||||
// The linkview, in handover form if this has not been attached to the main
|
||||
// SharedGroup yet
|
||||
LinkViewRef m_lv;
|
||||
std::unique_ptr<SharedGroup::Handover<LinkView>> m_lv_handover;
|
||||
CollectionChangeIndices m_change;
|
||||
|
||||
// The last-seen size of the LinkView so that we can report row deletions
|
||||
// when the LinkView itself is deleted
|
||||
size_t m_prev_size;
|
||||
|
||||
// The column index of the LinkView
|
||||
size_t m_col_ndx;
|
||||
std::vector<size_t> m_relevant_tables;
|
||||
|
||||
// The actual change, calculated in run() and delivered in prepare_handover()
|
||||
CollectionChangeIndices m_change;
|
||||
TransactionChangeInfo* m_info;
|
||||
|
||||
void run() override;
|
||||
|
|
|
@ -269,8 +269,8 @@ void RealmCoordinator::pin_version(uint_fast64_t version, uint_fast32_t index)
|
|||
m_advancer_history = nullptr;
|
||||
}
|
||||
}
|
||||
else if (m_new_queries.empty()) {
|
||||
// If this is the first query then we don't already have a read transaction
|
||||
else if (m_new_notifiers.empty()) {
|
||||
// If this is the first notifier then we don't already have a read transaction
|
||||
m_advancer_sg->begin_read(versionid);
|
||||
}
|
||||
else if (versionid < m_advancer_sg->get_version_of_current_transaction()) {
|
||||
|
@ -281,18 +281,18 @@ void RealmCoordinator::pin_version(uint_fast64_t version, uint_fast32_t index)
|
|||
}
|
||||
}
|
||||
|
||||
void RealmCoordinator::register_query(std::shared_ptr<BackgroundCollection> query)
|
||||
void RealmCoordinator::register_notifier(std::shared_ptr<BackgroundCollection> notifier)
|
||||
{
|
||||
auto version = query->version();
|
||||
auto& self = Realm::Internal::get_coordinator(query->get_realm());
|
||||
auto version = notifier->version();
|
||||
auto& self = Realm::Internal::get_coordinator(*notifier->get_realm());
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(self.m_query_mutex);
|
||||
std::lock_guard<std::mutex> lock(self.m_notifier_mutex);
|
||||
self.pin_version(version.version, version.index);
|
||||
self.m_new_queries.push_back(std::move(query));
|
||||
self.m_new_notifiers.push_back(std::move(notifier));
|
||||
}
|
||||
}
|
||||
|
||||
void RealmCoordinator::clean_up_dead_queries()
|
||||
void RealmCoordinator::clean_up_dead_notifiers()
|
||||
{
|
||||
auto swap_remove = [&](auto& container) {
|
||||
bool did_remove = false;
|
||||
|
@ -300,8 +300,8 @@ void RealmCoordinator::clean_up_dead_queries()
|
|||
if (container[i]->is_alive())
|
||||
continue;
|
||||
|
||||
// Ensure the query is destroyed here even if there's lingering refs
|
||||
// to the async query elsewhere
|
||||
// Ensure the notifier is destroyed here even if there's lingering refs
|
||||
// to the async notifier elsewhere
|
||||
container[i]->release_data();
|
||||
|
||||
if (container.size() > i + 1)
|
||||
|
@ -313,16 +313,16 @@ void RealmCoordinator::clean_up_dead_queries()
|
|||
return did_remove;
|
||||
};
|
||||
|
||||
if (swap_remove(m_queries)) {
|
||||
if (swap_remove(m_notifiers)) {
|
||||
// Make sure we aren't holding on to read versions needlessly if there
|
||||
// are no queries left, but don't close them entirely as opening shared
|
||||
// are no notifiers left, but don't close them entirely as opening shared
|
||||
// groups is expensive
|
||||
if (m_queries.empty() && m_query_sg) {
|
||||
m_query_sg->end_read();
|
||||
if (m_notifiers.empty() && m_notifier_sg) {
|
||||
m_notifier_sg->end_read();
|
||||
}
|
||||
}
|
||||
if (swap_remove(m_new_queries)) {
|
||||
if (m_new_queries.empty() && m_advancer_sg) {
|
||||
if (swap_remove(m_new_notifiers)) {
|
||||
if (m_new_notifiers.empty() && m_advancer_sg) {
|
||||
m_advancer_sg->end_read();
|
||||
}
|
||||
}
|
||||
|
@ -330,7 +330,7 @@ void RealmCoordinator::clean_up_dead_queries()
|
|||
|
||||
void RealmCoordinator::on_change()
|
||||
{
|
||||
run_async_queries();
|
||||
run_async_notifiers();
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_realm_mutex);
|
||||
for (auto& realm : m_weak_realm_notifiers) {
|
||||
|
@ -338,13 +338,13 @@ void RealmCoordinator::on_change()
|
|||
}
|
||||
}
|
||||
|
||||
void RealmCoordinator::run_async_queries()
|
||||
void RealmCoordinator::run_async_notifiers()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_query_mutex);
|
||||
std::unique_lock<std::mutex> lock(m_notifier_mutex);
|
||||
|
||||
clean_up_dead_queries();
|
||||
clean_up_dead_notifiers();
|
||||
|
||||
if (m_queries.empty() && m_new_queries.empty()) {
|
||||
if (m_notifiers.empty() && m_new_notifiers.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -353,73 +353,73 @@ void RealmCoordinator::run_async_queries()
|
|||
}
|
||||
|
||||
if (m_async_error) {
|
||||
std::move(m_new_queries.begin(), m_new_queries.end(), std::back_inserter(m_queries));
|
||||
m_new_queries.clear();
|
||||
std::move(m_new_notifiers.begin(), m_new_notifiers.end(), std::back_inserter(m_notifiers));
|
||||
m_new_notifiers.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<TransactionChangeInfo> change_info;
|
||||
SharedGroup::VersionID version;
|
||||
|
||||
auto new_queries = std::move(m_new_queries);
|
||||
if (new_queries.empty()) {
|
||||
auto new_notifiers = std::move(m_new_notifiers);
|
||||
if (new_notifiers.empty()) {
|
||||
change_info.resize(1);
|
||||
}
|
||||
else {
|
||||
change_info.resize(2);
|
||||
|
||||
// Sort newly added queries by their source version so that we can pull them
|
||||
// Sort newly added notifiers by their source version so that we can pull them
|
||||
// all forward to the latest version in a single pass over the transaction log
|
||||
std::sort(new_queries.begin(), new_queries.end(),
|
||||
std::sort(new_notifiers.begin(), new_notifiers.end(),
|
||||
[](auto&& lft, auto&& rgt) { return lft->version() < rgt->version(); });
|
||||
version = m_advancer_sg->get_version_of_current_transaction();
|
||||
REALM_ASSERT(version == new_queries.front()->version());
|
||||
REALM_ASSERT(version == new_notifiers.front()->version());
|
||||
|
||||
TransactionChangeInfo* info = &change_info.back();
|
||||
|
||||
// Advance each of the new queries to the latest version, attaching them
|
||||
// Advance each of the new notifiers to the latest version, attaching them
|
||||
// to the SG at their handover version. This requires a unique
|
||||
// TransactionChangeInfo for each source version, so that things don't
|
||||
// see changes from before the version they were handed over from.
|
||||
// Each Info has all of the changes between that source version and the
|
||||
// next source version, and they'll be merged together later after
|
||||
// releasing the lock
|
||||
for (auto& query : new_queries) {
|
||||
if (version != query->version()) {
|
||||
transaction::advance_and_observe_linkviews(*m_advancer_sg, *info, query->version());
|
||||
for (auto& notifier : new_notifiers) {
|
||||
if (version != notifier->version()) {
|
||||
transaction::advance_and_observe_linkviews(*m_advancer_sg, *info, notifier->version());
|
||||
change_info.push_back({{}, std::move(info->lists)});
|
||||
info = &change_info.back();
|
||||
version = query->version();
|
||||
version = notifier->version();
|
||||
}
|
||||
query->attach_to(*m_advancer_sg);
|
||||
query->add_required_change_info(*info);
|
||||
notifier->attach_to(*m_advancer_sg);
|
||||
notifier->add_required_change_info(*info);
|
||||
}
|
||||
|
||||
transaction::advance_and_observe_linkviews(*m_advancer_sg, *info);
|
||||
|
||||
for (auto& query : new_queries) {
|
||||
query->detach();
|
||||
for (auto& notifier : new_notifiers) {
|
||||
notifier->detach();
|
||||
}
|
||||
version = m_advancer_sg->get_version_of_current_transaction();
|
||||
m_advancer_sg->end_read();
|
||||
}
|
||||
|
||||
// Make a copy of the queries vector and then release the lock to avoid
|
||||
// blocking other threads trying to register or unregister queries while we run them
|
||||
auto queries = m_queries;
|
||||
// Make a copy of the notifiers vector and then release the lock to avoid
|
||||
// blocking other threads trying to register or unregister notifiers while we run them
|
||||
auto notifiers = m_notifiers;
|
||||
lock.unlock();
|
||||
|
||||
for (auto& query : queries) {
|
||||
query->add_required_change_info(change_info[0]);
|
||||
for (auto& notifier : notifiers) {
|
||||
notifier->add_required_change_info(change_info[0]);
|
||||
}
|
||||
|
||||
transaction::advance_and_observe_linkviews(*m_query_sg, change_info[0], version);
|
||||
transaction::advance_and_observe_linkviews(*m_notifier_sg, change_info[0], version);
|
||||
|
||||
// Attach the new queries to the main SG and move them to the main list
|
||||
for (auto& query : new_queries) {
|
||||
query->attach_to(*m_query_sg);
|
||||
// Attach the new notifiers to the main SG and move them to the main list
|
||||
for (auto& notifier : new_notifiers) {
|
||||
notifier->attach_to(*m_notifier_sg);
|
||||
}
|
||||
std::move(new_queries.begin(), new_queries.end(), std::back_inserter(queries));
|
||||
std::move(new_notifiers.begin(), new_notifiers.end(), std::back_inserter(notifiers));
|
||||
|
||||
for (size_t i = change_info.size() - 1; i > 1; --i) {
|
||||
auto& cur = change_info[i];
|
||||
|
@ -452,57 +452,57 @@ void RealmCoordinator::run_async_queries()
|
|||
}
|
||||
}
|
||||
|
||||
for (auto& query : queries) {
|
||||
query->run();
|
||||
for (auto& notifier : notifiers) {
|
||||
notifier->run();
|
||||
}
|
||||
|
||||
// Reacquire the lock while updating the fields that are actually read on
|
||||
// other threads
|
||||
lock.lock();
|
||||
for (auto& query : queries) {
|
||||
query->prepare_handover();
|
||||
for (auto& notifier : notifiers) {
|
||||
notifier->prepare_handover();
|
||||
}
|
||||
m_queries = std::move(queries);
|
||||
clean_up_dead_queries();
|
||||
m_notifiers = std::move(notifiers);
|
||||
clean_up_dead_notifiers();
|
||||
}
|
||||
|
||||
void RealmCoordinator::open_helper_shared_group()
|
||||
{
|
||||
if (!m_query_sg) {
|
||||
if (!m_notifier_sg) {
|
||||
try {
|
||||
std::unique_ptr<Group> read_only_group;
|
||||
Realm::open_with_config(m_config, m_query_history, m_query_sg, read_only_group);
|
||||
Realm::open_with_config(m_config, m_notifier_history, m_notifier_sg, read_only_group);
|
||||
REALM_ASSERT(!read_only_group);
|
||||
m_query_sg->begin_read();
|
||||
m_notifier_sg->begin_read();
|
||||
}
|
||||
catch (...) {
|
||||
// Store the error to be passed to the async queries
|
||||
// Store the error to be passed to the async notifiers
|
||||
m_async_error = std::current_exception();
|
||||
m_query_sg = nullptr;
|
||||
m_query_history = nullptr;
|
||||
m_notifier_sg = nullptr;
|
||||
m_notifier_history = nullptr;
|
||||
}
|
||||
}
|
||||
else if (m_queries.empty()) {
|
||||
m_query_sg->begin_read();
|
||||
else if (m_notifiers.empty()) {
|
||||
m_notifier_sg->begin_read();
|
||||
}
|
||||
}
|
||||
|
||||
void RealmCoordinator::move_new_queries_to_main()
|
||||
void RealmCoordinator::move_new_notifiers_to_main()
|
||||
{
|
||||
m_queries.reserve(m_queries.size() + m_new_queries.size());
|
||||
std::move(m_new_queries.begin(), m_new_queries.end(), std::back_inserter(m_queries));
|
||||
m_new_queries.clear();
|
||||
m_notifiers.reserve(m_notifiers.size() + m_new_notifiers.size());
|
||||
std::move(m_new_notifiers.begin(), m_new_notifiers.end(), std::back_inserter(m_notifiers));
|
||||
m_new_notifiers.clear();
|
||||
}
|
||||
|
||||
void RealmCoordinator::advance_to_ready(Realm& realm)
|
||||
{
|
||||
decltype(m_queries) queries;
|
||||
decltype(m_notifiers) notifiers;
|
||||
|
||||
auto& sg = Realm::Internal::get_shared_group(realm);
|
||||
|
||||
auto get_query_version = [&] {
|
||||
for (auto& query : m_queries) {
|
||||
auto version = query->version();
|
||||
auto get_notifier_version = [&] {
|
||||
for (auto& notifier : m_notifiers) {
|
||||
auto version = notifier->version();
|
||||
if (version != SharedGroup::VersionID{}) {
|
||||
return version;
|
||||
}
|
||||
|
@ -512,11 +512,11 @@ void RealmCoordinator::advance_to_ready(Realm& realm)
|
|||
|
||||
SharedGroup::VersionID version;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_query_mutex);
|
||||
version = get_query_version();
|
||||
std::lock_guard<std::mutex> lock(m_notifier_mutex);
|
||||
version = get_notifier_version();
|
||||
}
|
||||
|
||||
// no async queries; just advance to latest
|
||||
// no async notifiers; just advance to latest
|
||||
if (version.version == std::numeric_limits<uint_fast64_t>::max()) {
|
||||
transaction::advance(sg, realm.m_binding_context.get());
|
||||
return;
|
||||
|
@ -532,44 +532,44 @@ void RealmCoordinator::advance_to_ready(Realm& realm)
|
|||
// may end up calling user code (in did_change() notifications)
|
||||
transaction::advance(sg, realm.m_binding_context.get(), version);
|
||||
|
||||
// Reacquire the lock and recheck the query version, as the queries may
|
||||
// Reacquire the lock and recheck the notifier version, as the notifiers may
|
||||
// have advanced to a later version while we didn't hold the lock. If
|
||||
// so, we need to release the lock and re-advance
|
||||
std::lock_guard<std::mutex> lock(m_query_mutex);
|
||||
version = get_query_version();
|
||||
std::lock_guard<std::mutex> lock(m_notifier_mutex);
|
||||
version = get_notifier_version();
|
||||
if (version.version == std::numeric_limits<uint_fast64_t>::max())
|
||||
return;
|
||||
if (version != sg.get_version_of_current_transaction())
|
||||
continue;
|
||||
|
||||
// Query version now matches the SG version, so we can deliver them
|
||||
for (auto& query : m_queries) {
|
||||
if (query->deliver(sg, m_async_error)) {
|
||||
queries.push_back(query);
|
||||
for (auto& notifier : m_notifiers) {
|
||||
if (notifier->deliver(sg, m_async_error)) {
|
||||
notifiers.push_back(notifier);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
for (auto& query : queries) {
|
||||
query->call_callbacks();
|
||||
for (auto& notifier : notifiers) {
|
||||
notifier->call_callbacks();
|
||||
}
|
||||
}
|
||||
|
||||
void RealmCoordinator::process_available_async(Realm& realm)
|
||||
{
|
||||
auto& sg = Realm::Internal::get_shared_group(realm);
|
||||
decltype(m_queries) queries;
|
||||
decltype(m_notifiers) notifiers;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_query_mutex);
|
||||
for (auto& query : m_queries) {
|
||||
if (query->deliver(sg, m_async_error)) {
|
||||
queries.push_back(query);
|
||||
std::lock_guard<std::mutex> lock(m_notifier_mutex);
|
||||
for (auto& notifier : m_notifiers) {
|
||||
if (notifier->deliver(sg, m_async_error)) {
|
||||
notifiers.push_back(notifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& query : queries) {
|
||||
query->call_callbacks();
|
||||
for (auto& notifier : notifiers) {
|
||||
notifier->call_callbacks();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,7 +102,7 @@ public:
|
|||
// Update the schema in the cached config
|
||||
void update_schema(Schema const& new_schema);
|
||||
|
||||
static void register_query(std::shared_ptr<BackgroundCollection> query);
|
||||
static void register_notifier(std::shared_ptr<BackgroundCollection> notifier);
|
||||
|
||||
// Advance the Realm to the most recent transaction version which all async
|
||||
// work is complete for
|
||||
|
@ -115,32 +115,32 @@ private:
|
|||
std::mutex m_realm_mutex;
|
||||
std::vector<WeakRealmNotifier> m_weak_realm_notifiers;
|
||||
|
||||
std::mutex m_query_mutex;
|
||||
std::vector<std::shared_ptr<_impl::BackgroundCollection>> m_new_queries;
|
||||
std::vector<std::shared_ptr<_impl::BackgroundCollection>> m_queries;
|
||||
std::mutex m_notifier_mutex;
|
||||
std::vector<std::shared_ptr<_impl::BackgroundCollection>> m_new_notifiers;
|
||||
std::vector<std::shared_ptr<_impl::BackgroundCollection>> m_notifiers;
|
||||
|
||||
// SharedGroup used for actually running async queries
|
||||
// Will have a read transaction iff m_queries is non-empty
|
||||
std::unique_ptr<Replication> m_query_history;
|
||||
std::unique_ptr<SharedGroup> m_query_sg;
|
||||
// SharedGroup used for actually running async notifiers
|
||||
// Will have a read transaction iff m_notifiers is non-empty
|
||||
std::unique_ptr<Replication> m_notifier_history;
|
||||
std::unique_ptr<SharedGroup> m_notifier_sg;
|
||||
|
||||
// SharedGroup used to advance queries in m_new_queries to the main shared
|
||||
// SharedGroup used to advance notifiers in m_new_notifiers to the main shared
|
||||
// group's transaction version
|
||||
// Will have a read transaction iff m_new_queries is non-empty
|
||||
// Will have a read transaction iff m_new_notifiers is non-empty
|
||||
std::unique_ptr<Replication> m_advancer_history;
|
||||
std::unique_ptr<SharedGroup> m_advancer_sg;
|
||||
std::exception_ptr m_async_error;
|
||||
|
||||
std::unique_ptr<_impl::ExternalCommitHelper> m_notifier;
|
||||
|
||||
// must be called with m_query_mutex locked
|
||||
// must be called with m_notifier_mutex locked
|
||||
void pin_version(uint_fast64_t version, uint_fast32_t index);
|
||||
|
||||
void run_async_queries();
|
||||
void run_async_notifiers();
|
||||
void open_helper_shared_group();
|
||||
void move_new_queries_to_main();
|
||||
void move_new_notifiers_to_main();
|
||||
void advance_helper_shared_group_to_latest();
|
||||
void clean_up_dead_queries();
|
||||
void clean_up_dead_notifiers();
|
||||
};
|
||||
|
||||
} // namespace _impl
|
||||
|
|
|
@ -31,7 +31,7 @@ ResultsNotifier::ResultsNotifier(Results& target)
|
|||
{
|
||||
Query q = target.get_query();
|
||||
set_table(*q.get_table());
|
||||
m_query_handover = Realm::Internal::get_shared_group(get_realm()).export_for_handover(q, MutableSourcePayload::Move);
|
||||
m_query_handover = Realm::Internal::get_shared_group(*get_realm()).export_for_handover(q, MutableSourcePayload::Move);
|
||||
}
|
||||
|
||||
void ResultsNotifier::release_data() noexcept
|
||||
|
@ -39,30 +39,6 @@ void ResultsNotifier::release_data() noexcept
|
|||
m_query = nullptr;
|
||||
}
|
||||
|
||||
// Most of the inter-thread synchronization for run(), prepare_handover(),
|
||||
// attach_to(), detach(), release_query() and deliver() is done by
|
||||
// RealmCoordinator external to this code, which has some potentially
|
||||
// non-obvious results on which members are and are not safe to use without
|
||||
// holding a lock.
|
||||
//
|
||||
// attach_to(), detach(), run(), prepare_handover(), and release_query() are
|
||||
// all only ever called on a single thread. call_callbacks() and deliver() are
|
||||
// called on the same thread. Calls to prepare_handover() and deliver() are
|
||||
// guarded by a lock.
|
||||
//
|
||||
// In total, this means that the safe data flow is as follows:
|
||||
// - prepare_handover(), attach_to(), detach() and release_query() can read
|
||||
// members written by each other
|
||||
// - deliver() can read members written to in prepare_handover(), deliver(),
|
||||
// and call_callbacks()
|
||||
// - call_callbacks() and read members written to in deliver()
|
||||
//
|
||||
// Separately from this data flow for the query results, all uses of
|
||||
// m_target_results, m_callbacks, and m_callback_index must be done with the
|
||||
// appropriate mutex held to avoid race conditions when the Results object is
|
||||
// destroyed while the background work is running, and to allow removing
|
||||
// callbacks from any thread.
|
||||
|
||||
static bool map_moves(size_t& idx, CollectionChangeIndices const& changes)
|
||||
{
|
||||
for (auto&& move : changes.moves) {
|
||||
|
@ -74,6 +50,27 @@ static bool map_moves(size_t& idx, CollectionChangeIndices const& changes)
|
|||
return false;
|
||||
}
|
||||
|
||||
// Most of the inter-thread synchronization for run(), prepare_handover(),
|
||||
// attach_to(), detach(), release_data() and deliver() is done by
|
||||
// RealmCoordinator external to this code, which has some potentially
|
||||
// non-obvious results on which members are and are not safe to use without
|
||||
// holding a lock.
|
||||
//
|
||||
// add_required_change_info(), attach_to(), detach(), run(),
|
||||
// prepare_handover(), and release_data() are all only ever called on a single
|
||||
// background worker thread. call_callbacks() and deliver() are called on the
|
||||
// target thread. Calls to prepare_handover() and deliver() are guarded by a
|
||||
// lock.
|
||||
//
|
||||
// In total, this means that the safe data flow is as follows:
|
||||
// - add_Required_change_info(), prepare_handover(), attach_to(), detach() and
|
||||
// release_data() can read members written by each other
|
||||
// - deliver() can read members written to in prepare_handover(), deliver(),
|
||||
// and call_callbacks()
|
||||
// - call_callbacks() and read members written to in deliver()
|
||||
//
|
||||
// Separately from the handover data flow, m_target_results is guarded by the target lock
|
||||
|
||||
void ResultsNotifier::do_add_required_change_info(TransactionChangeInfo& info)
|
||||
{
|
||||
REALM_ASSERT(m_query);
|
||||
|
@ -83,24 +80,21 @@ void ResultsNotifier::do_add_required_change_info(TransactionChangeInfo& info)
|
|||
void ResultsNotifier::run()
|
||||
{
|
||||
REALM_ASSERT(m_info);
|
||||
REALM_ASSERT(!m_tv.is_attached());
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> target_lock(m_target_mutex);
|
||||
auto lock = lock_target();
|
||||
// Don't run the query if the results aren't actually going to be used
|
||||
if (!m_target_results || (!have_callbacks() && !m_target_results->wants_background_updates())) {
|
||||
if (!get_realm() || (!have_callbacks() && !m_target_results->wants_background_updates())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
REALM_ASSERT(!m_tv.is_attached());
|
||||
|
||||
size_t table_ndx = m_query->get_table()->get_index_in_group();
|
||||
|
||||
// If we've run previously, check if we need to rerun
|
||||
if (m_initial_run_complete) {
|
||||
// Make an empty tableview from the query to get the table version, since
|
||||
// Query doesn't expose it
|
||||
if (m_query->find_all(0, 0, 0).sync_if_needed() == m_handed_over_table_version) {
|
||||
if (m_query->find_all(0, 0, 0).sync_if_needed() == m_last_seen_version) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -109,7 +103,9 @@ void ResultsNotifier::run()
|
|||
if (m_sort) {
|
||||
m_tv.sort(m_sort.column_indices, m_sort.ascending);
|
||||
}
|
||||
m_last_seen_version = m_tv.sync_if_needed();
|
||||
|
||||
size_t table_ndx = m_query->get_table()->get_index_in_group();
|
||||
if (m_initial_run_complete) {
|
||||
auto changes = table_ndx < m_info->tables.size() ? &m_info->tables[table_ndx] : nullptr;
|
||||
|
||||
|
@ -156,7 +152,6 @@ void ResultsNotifier::do_prepare_handover(SharedGroup& sg)
|
|||
REALM_ASSERT(m_tv.is_in_sync());
|
||||
|
||||
m_initial_run_complete = true;
|
||||
m_handed_over_table_version = m_tv.sync_if_needed();
|
||||
m_tv_handover = sg.export_for_handover(m_tv, MutableSourcePayload::Move);
|
||||
|
||||
add_changes(std::move(m_changes));
|
||||
|
@ -169,7 +164,7 @@ void ResultsNotifier::do_prepare_handover(SharedGroup& sg)
|
|||
|
||||
bool ResultsNotifier::do_deliver(SharedGroup& sg)
|
||||
{
|
||||
std::lock_guard<std::mutex> target_lock(m_target_mutex);
|
||||
auto lock = lock_target();
|
||||
|
||||
// Target results being null here indicates that it was destroyed while we
|
||||
// were in the process of advancing the Realm version and preparing for
|
||||
|
|
|
@ -24,12 +24,8 @@
|
|||
|
||||
#include <realm/group_shared.hpp>
|
||||
|
||||
#include <exception>
|
||||
#include <mutex>
|
||||
#include <functional>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace realm {
|
||||
namespace _impl {
|
||||
|
@ -40,24 +36,8 @@ public:
|
|||
ResultsNotifier(Results& target);
|
||||
|
||||
private:
|
||||
// Run/rerun the query if needed
|
||||
void run() override;
|
||||
// Prepare the handover object if run() did update the TableView
|
||||
void do_prepare_handover(SharedGroup&) override;
|
||||
// Update the target results from the handover
|
||||
// Returns if any callbacks need to be invoked
|
||||
bool do_deliver(SharedGroup& sg) override;
|
||||
|
||||
void do_add_required_change_info(TransactionChangeInfo& info) override;
|
||||
|
||||
void release_data() noexcept override;
|
||||
void do_attach_to(SharedGroup& sg) override;
|
||||
void do_detach_from(SharedGroup& sg) override;
|
||||
|
||||
bool should_deliver_initial() const noexcept override { return true; }
|
||||
|
||||
// Target Results to update and a mutex which guards it
|
||||
mutable std::mutex m_target_mutex;
|
||||
// Target Results to update
|
||||
// Can only be used with lock_target() held
|
||||
Results* m_target_results;
|
||||
|
||||
const SortOrder m_sort;
|
||||
|
@ -71,14 +51,31 @@ private:
|
|||
TableView m_tv;
|
||||
std::unique_ptr<SharedGroup::Handover<TableView>> m_tv_handover;
|
||||
|
||||
// The table version from the last time the query was run. Used to avoid
|
||||
// rerunning the query when there's no chance of it changing.
|
||||
uint_fast64_t m_last_seen_version = -1;
|
||||
|
||||
// The rows from the previous run of the query, for calculating diffs
|
||||
std::vector<size_t> m_previous_rows;
|
||||
|
||||
// The changeset calculated during run() and delivered in do_prepare_handover()
|
||||
CollectionChangeIndices m_changes;
|
||||
TransactionChangeInfo* m_info = nullptr;
|
||||
|
||||
uint_fast64_t m_handed_over_table_version = -1;
|
||||
|
||||
std::vector<size_t> m_previous_rows;
|
||||
|
||||
// Flag for whether or not the query has been run at all, as goofy timing
|
||||
// can lead to deliver() being called before that
|
||||
bool m_initial_run_complete = false;
|
||||
|
||||
void run() override;
|
||||
void do_prepare_handover(SharedGroup&) override;
|
||||
bool do_deliver(SharedGroup& sg) override;
|
||||
void do_add_required_change_info(TransactionChangeInfo& info) override;
|
||||
|
||||
void release_data() noexcept override;
|
||||
void do_attach_to(SharedGroup& sg) override;
|
||||
void do_detach_from(SharedGroup& sg) override;
|
||||
|
||||
bool should_deliver_initial() const noexcept override { return true; }
|
||||
};
|
||||
|
||||
} // namespace _impl
|
||||
|
|
|
@ -176,7 +176,7 @@ NotificationToken List::add_notification_callback(CollectionChangeCallback cb)
|
|||
verify_attached();
|
||||
if (!m_notifier) {
|
||||
m_notifier = std::make_shared<ListNotifier>(m_link_view, m_realm);
|
||||
RealmCoordinator::register_query(m_notifier);
|
||||
RealmCoordinator::register_notifier(m_notifier);
|
||||
}
|
||||
return {m_notifier, m_notifier->add_callback(std::move(cb))};
|
||||
}
|
||||
|
|
|
@ -59,8 +59,8 @@ Results::Results(SharedRealm r, const ObjectSchema &o, Table& table)
|
|||
|
||||
Results::~Results()
|
||||
{
|
||||
if (m_background_query) {
|
||||
m_background_query->unregister();
|
||||
if (m_notifier) {
|
||||
m_notifier->unregister();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -181,9 +181,9 @@ void Results::update_tableview()
|
|||
if (!m_live) {
|
||||
return;
|
||||
}
|
||||
if (!m_background_query && !m_realm->is_in_transaction() && m_realm->can_deliver_notifications()) {
|
||||
m_background_query = std::make_shared<_impl::ResultsNotifier>(*this);
|
||||
_impl::RealmCoordinator::register_query(m_background_query);
|
||||
if (!m_notifier && !m_realm->is_in_transaction() && m_realm->can_deliver_notifications()) {
|
||||
m_notifier = std::make_shared<_impl::ResultsNotifier>(*this);
|
||||
_impl::RealmCoordinator::register_notifier(m_notifier);
|
||||
}
|
||||
m_has_used_table_view = true;
|
||||
m_table_view.sync_if_needed();
|
||||
|
@ -369,9 +369,9 @@ void Results::prepare_async()
|
|||
throw InvalidTransactionException("Cannot create asynchronous query while in a write transaction");
|
||||
}
|
||||
|
||||
if (!m_background_query) {
|
||||
m_background_query = std::make_shared<_impl::ResultsNotifier>(*this);
|
||||
_impl::RealmCoordinator::register_query(m_background_query);
|
||||
if (!m_notifier) {
|
||||
m_notifier = std::make_shared<_impl::ResultsNotifier>(*this);
|
||||
_impl::RealmCoordinator::register_notifier(m_notifier);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -379,13 +379,13 @@ NotificationToken Results::async(std::function<void (std::exception_ptr)> target
|
|||
{
|
||||
prepare_async();
|
||||
auto wrap = [=](CollectionChangeIndices, std::exception_ptr e) { target(e); };
|
||||
return {m_background_query, m_background_query->add_callback(wrap)};
|
||||
return {m_notifier, m_notifier->add_callback(wrap)};
|
||||
}
|
||||
|
||||
NotificationToken Results::add_notification_callback(CollectionChangeCallback cb)
|
||||
{
|
||||
prepare_async();
|
||||
return {m_background_query, m_background_query->add_callback(std::move(cb))};
|
||||
return {m_notifier, m_notifier->add_callback(std::move(cb))};
|
||||
}
|
||||
|
||||
void Results::Internal::set_table_view(Results& results, realm::TableView &&tv)
|
||||
|
|
|
@ -197,7 +197,7 @@ private:
|
|||
SortOrder m_sort;
|
||||
bool m_live = true;
|
||||
|
||||
std::shared_ptr<_impl::ResultsNotifier> m_background_query;
|
||||
std::shared_ptr<_impl::ResultsNotifier> m_notifier;
|
||||
|
||||
Mode m_mode = Mode::Empty;
|
||||
bool m_has_used_table_view = false;
|
||||
|
|
Loading…
Reference in New Issue