Extract out the parts of AsyncQuery not directly related to query running
This commit is contained in:
parent
deea1e8f5f
commit
6380335fc3
|
@ -7,6 +7,7 @@ set(SOURCES
|
||||||
schema.cpp
|
schema.cpp
|
||||||
shared_realm.cpp
|
shared_realm.cpp
|
||||||
impl/async_query.cpp
|
impl/async_query.cpp
|
||||||
|
impl/background_collection.cpp
|
||||||
impl/realm_coordinator.cpp
|
impl/realm_coordinator.cpp
|
||||||
impl/transact_log_handler.cpp
|
impl/transact_log_handler.cpp
|
||||||
parser/parser.cpp
|
parser/parser.cpp
|
||||||
|
@ -20,10 +21,11 @@ set(HEADERS
|
||||||
results.hpp
|
results.hpp
|
||||||
schema.hpp
|
schema.hpp
|
||||||
shared_realm.hpp
|
shared_realm.hpp
|
||||||
impl/weak_realm_notifier.hpp
|
impl/background_collection.hpp
|
||||||
impl/weak_realm_notifier_base.hpp
|
|
||||||
impl/external_commit_helper.hpp
|
impl/external_commit_helper.hpp
|
||||||
impl/transact_log_handler.hpp
|
impl/transact_log_handler.hpp
|
||||||
|
impl/weak_realm_notifier.hpp
|
||||||
|
impl/weak_realm_notifier_base.hpp
|
||||||
parser/parser.hpp
|
parser/parser.hpp
|
||||||
parser/query_builder.hpp
|
parser/query_builder.hpp
|
||||||
util/atomic_shared_ptr.hpp)
|
util/atomic_shared_ptr.hpp)
|
||||||
|
|
|
@ -25,98 +25,19 @@ using namespace realm;
|
||||||
using namespace realm::_impl;
|
using namespace realm::_impl;
|
||||||
|
|
||||||
AsyncQuery::AsyncQuery(Results& target)
|
AsyncQuery::AsyncQuery(Results& target)
|
||||||
: m_target_results(&target)
|
: BackgroundCollection(target.get_realm())
|
||||||
, m_realm(target.get_realm())
|
, m_target_results(&target)
|
||||||
, m_sort(target.get_sort())
|
, m_sort(target.get_sort())
|
||||||
, m_sg_version(Realm::Internal::get_shared_group(*m_realm).get_version_of_current_transaction())
|
|
||||||
{
|
{
|
||||||
Query q = target.get_query();
|
Query q = target.get_query();
|
||||||
m_query_handover = Realm::Internal::get_shared_group(*m_realm).export_for_handover(q, MutableSourcePayload::Move);
|
m_query_handover = Realm::Internal::get_shared_group(get_realm()).export_for_handover(q, MutableSourcePayload::Move);
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncQuery::~AsyncQuery()
|
void AsyncQuery::release_data() noexcept
|
||||||
{
|
{
|
||||||
// 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_target_mutex);
|
|
||||||
m_realm = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AsyncQuery::add_callback(std::function<void (std::exception_ptr)> callback)
|
|
||||||
{
|
|
||||||
m_realm->verify_thread();
|
|
||||||
|
|
||||||
auto next_token = [=] {
|
|
||||||
size_t token = 0;
|
|
||||||
for (auto& callback : m_callbacks) {
|
|
||||||
if (token <= callback.token) {
|
|
||||||
token = callback.token + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return token;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(m_callback_mutex);
|
|
||||||
auto token = next_token();
|
|
||||||
m_callbacks.push_back({std::move(callback), token, -1ULL});
|
|
||||||
if (m_callback_index == npos) { // Don't need to wake up if we're already sending notifications
|
|
||||||
Realm::Internal::get_coordinator(*m_realm).send_commit_notifications();
|
|
||||||
m_have_callbacks = true;
|
|
||||||
}
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncQuery::remove_callback(size_t token)
|
|
||||||
{
|
|
||||||
Callback old;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(m_callback_mutex);
|
|
||||||
REALM_ASSERT(m_error || m_callbacks.size() > 0);
|
|
||||||
|
|
||||||
auto it = find_if(begin(m_callbacks), end(m_callbacks),
|
|
||||||
[=](const auto& c) { return c.token == token; });
|
|
||||||
// We should only fail to find the callback if it was removed due to an error
|
|
||||||
REALM_ASSERT(m_error || it != end(m_callbacks));
|
|
||||||
if (it == end(m_callbacks)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t idx = distance(begin(m_callbacks), it);
|
|
||||||
if (m_callback_index != npos && m_callback_index >= idx) {
|
|
||||||
--m_callback_index;
|
|
||||||
}
|
|
||||||
|
|
||||||
old = std::move(*it);
|
|
||||||
m_callbacks.erase(it);
|
|
||||||
|
|
||||||
m_have_callbacks = !m_callbacks.empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncQuery::unregister() noexcept
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(m_target_mutex);
|
|
||||||
m_target_results = nullptr;
|
|
||||||
m_realm = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncQuery::release_query() noexcept
|
|
||||||
{
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(m_target_mutex);
|
|
||||||
REALM_ASSERT(!m_realm && !m_target_results);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_query = nullptr;
|
m_query = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AsyncQuery::is_alive() const noexcept
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(m_target_mutex);
|
|
||||||
return m_target_results != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Most of the inter-thread synchronization for run(), prepare_handover(),
|
// Most of the inter-thread synchronization for run(), prepare_handover(),
|
||||||
// attach_to(), detach(), release_query() and deliver() is done by
|
// attach_to(), detach(), release_query() and deliver() is done by
|
||||||
// RealmCoordinator external to this code, which has some potentially
|
// RealmCoordinator external to this code, which has some potentially
|
||||||
|
@ -143,12 +64,12 @@ bool AsyncQuery::is_alive() const noexcept
|
||||||
|
|
||||||
void AsyncQuery::run()
|
void AsyncQuery::run()
|
||||||
{
|
{
|
||||||
REALM_ASSERT(m_sg);
|
m_did_change = false;
|
||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> target_lock(m_target_mutex);
|
std::lock_guard<std::mutex> target_lock(m_target_mutex);
|
||||||
// Don't run the query if the results aren't actually going to be used
|
// Don't run the query if the results aren't actually going to be used
|
||||||
if (!m_target_results || (!m_have_callbacks && !m_target_results->wants_background_updates())) {
|
if (!m_target_results || (!have_callbacks() && !m_target_results->wants_background_updates())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,33 +89,31 @@ void AsyncQuery::run()
|
||||||
if (m_sort) {
|
if (m_sort) {
|
||||||
m_tv.sort(m_sort.columnIndices, m_sort.ascending);
|
m_tv.sort(m_sort.columnIndices, m_sort.ascending);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_did_change = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncQuery::prepare_handover()
|
bool AsyncQuery::do_prepare_handover(SharedGroup& sg)
|
||||||
{
|
{
|
||||||
m_sg_version = m_sg->get_version_of_current_transaction();
|
|
||||||
|
|
||||||
if (!m_tv.is_attached()) {
|
if (!m_tv.is_attached()) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
REALM_ASSERT(m_tv.is_in_sync());
|
REALM_ASSERT(m_tv.is_in_sync());
|
||||||
|
|
||||||
m_initial_run_complete = true;
|
m_initial_run_complete = true;
|
||||||
m_handed_over_table_version = m_tv.sync_if_needed();
|
m_handed_over_table_version = m_tv.sync_if_needed();
|
||||||
m_tv_handover = m_sg->export_for_handover(m_tv, MutableSourcePayload::Move);
|
m_tv_handover = sg.export_for_handover(m_tv, MutableSourcePayload::Move);
|
||||||
|
|
||||||
// detach the TableView as we won't need it again and keeping it around
|
// detach the TableView as we won't need it again and keeping it around
|
||||||
// makes advance_read() much more expensive
|
// makes advance_read() much more expensive
|
||||||
m_tv = TableView();
|
m_tv = TableView();
|
||||||
|
|
||||||
|
return m_did_change;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AsyncQuery::deliver(SharedGroup& sg, std::exception_ptr err)
|
bool AsyncQuery::do_deliver(SharedGroup& sg)
|
||||||
{
|
{
|
||||||
if (!is_for_current_thread()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> target_lock(m_target_mutex);
|
std::lock_guard<std::mutex> target_lock(m_target_mutex);
|
||||||
|
|
||||||
// Target results being null here indicates that it was destroyed while we
|
// Target results being null here indicates that it was destroyed while we
|
||||||
|
@ -207,84 +126,32 @@ bool AsyncQuery::deliver(SharedGroup& sg, std::exception_ptr err)
|
||||||
// We can get called before the query has actually had the chance to run if
|
// We can get called before the query has actually had the chance to run if
|
||||||
// we're added immediately before a different set of async results are
|
// we're added immediately before a different set of async results are
|
||||||
// delivered
|
// delivered
|
||||||
if (!m_initial_run_complete && !err) {
|
if (!m_initial_run_complete) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err) {
|
|
||||||
m_error = err;
|
|
||||||
return m_have_callbacks;
|
|
||||||
}
|
|
||||||
|
|
||||||
REALM_ASSERT(!m_query_handover);
|
REALM_ASSERT(!m_query_handover);
|
||||||
|
|
||||||
auto realm_sg_version = Realm::Internal::get_shared_group(*m_realm).get_version_of_current_transaction();
|
|
||||||
if (m_sg_version != realm_sg_version) {
|
|
||||||
// Realm version can be newer if a commit was made on our thread or the
|
|
||||||
// user manually called refresh(), or older if a commit was made on a
|
|
||||||
// different thread and we ran *really* fast in between the check for
|
|
||||||
// if the shared group has changed and when we pick up async results
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_tv_handover) {
|
if (m_tv_handover) {
|
||||||
m_tv_handover->version = m_sg_version;
|
m_tv_handover->version = version();
|
||||||
Results::Internal::set_table_view(*m_target_results,
|
Results::Internal::set_table_view(*m_target_results,
|
||||||
std::move(*sg.import_from_handover(std::move(m_tv_handover))));
|
std::move(*sg.import_from_handover(std::move(m_tv_handover))));
|
||||||
m_delivered_table_version = m_handed_over_table_version;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
REALM_ASSERT(!m_tv_handover);
|
REALM_ASSERT(!m_tv_handover);
|
||||||
return m_have_callbacks;
|
return have_callbacks();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncQuery::call_callbacks()
|
void AsyncQuery::do_attach_to(SharedGroup& sg)
|
||||||
{
|
{
|
||||||
REALM_ASSERT(is_for_current_thread());
|
|
||||||
|
|
||||||
while (auto fn = next_callback()) {
|
|
||||||
fn(m_error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_error) {
|
|
||||||
// Remove all the callbacks as we never need to call anything ever again
|
|
||||||
// after delivering an error
|
|
||||||
std::lock_guard<std::mutex> callback_lock(m_callback_mutex);
|
|
||||||
m_callbacks.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::function<void (std::exception_ptr)> AsyncQuery::next_callback()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> callback_lock(m_callback_mutex);
|
|
||||||
for (++m_callback_index; m_callback_index < m_callbacks.size(); ++m_callback_index) {
|
|
||||||
auto& callback = m_callbacks[m_callback_index];
|
|
||||||
if (m_error || callback.delivered_version != m_delivered_table_version) {
|
|
||||||
callback.delivered_version = m_delivered_table_version;
|
|
||||||
return callback.fn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_callback_index = npos;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncQuery::attach_to(realm::SharedGroup& sg)
|
|
||||||
{
|
|
||||||
REALM_ASSERT(!m_sg);
|
|
||||||
REALM_ASSERT(m_query_handover);
|
REALM_ASSERT(m_query_handover);
|
||||||
|
|
||||||
m_query = sg.import_from_handover(std::move(m_query_handover));
|
m_query = sg.import_from_handover(std::move(m_query_handover));
|
||||||
m_sg = &sg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncQuery::detatch()
|
void AsyncQuery::do_detach_from(SharedGroup& sg)
|
||||||
{
|
{
|
||||||
REALM_ASSERT(m_sg);
|
|
||||||
REALM_ASSERT(m_query);
|
REALM_ASSERT(m_query);
|
||||||
REALM_ASSERT(!m_tv.is_attached());
|
REALM_ASSERT(!m_tv.is_attached());
|
||||||
|
|
||||||
m_query_handover = m_sg->export_for_handover(*m_query, MutableSourcePayload::Move);
|
m_query_handover = sg.export_for_handover(*m_query, MutableSourcePayload::Move);
|
||||||
m_sg = nullptr;
|
|
||||||
m_query = nullptr;
|
m_query = nullptr;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#ifndef REALM_ASYNC_QUERY_HPP
|
#ifndef REALM_ASYNC_QUERY_HPP
|
||||||
#define REALM_ASYNC_QUERY_HPP
|
#define REALM_ASYNC_QUERY_HPP
|
||||||
|
|
||||||
|
#include "background_collection.hpp"
|
||||||
#include "results.hpp"
|
#include "results.hpp"
|
||||||
|
|
||||||
#include <realm/group_shared.hpp>
|
#include <realm/group_shared.hpp>
|
||||||
|
@ -31,46 +32,28 @@
|
||||||
|
|
||||||
namespace realm {
|
namespace realm {
|
||||||
namespace _impl {
|
namespace _impl {
|
||||||
class AsyncQuery {
|
class AsyncQuery : public BackgroundCollection {
|
||||||
public:
|
public:
|
||||||
AsyncQuery(Results& target);
|
AsyncQuery(Results& target);
|
||||||
~AsyncQuery();
|
|
||||||
|
|
||||||
size_t add_callback(std::function<void (std::exception_ptr)>);
|
|
||||||
void remove_callback(size_t token);
|
|
||||||
|
|
||||||
void unregister() noexcept;
|
|
||||||
void release_query() noexcept;
|
|
||||||
|
|
||||||
// Run/rerun the query if needed
|
|
||||||
void run();
|
|
||||||
// Prepare the handover object if run() did update the TableView
|
|
||||||
void prepare_handover();
|
|
||||||
// Update the target results from the handover
|
|
||||||
// Returns if any callbacks need to be invoked
|
|
||||||
bool deliver(SharedGroup& sg, std::exception_ptr err);
|
|
||||||
void call_callbacks();
|
|
||||||
|
|
||||||
// Attach the handed-over query to `sg`
|
|
||||||
void attach_to(SharedGroup& sg);
|
|
||||||
// Create a new query handover object and stop using the previously attached
|
|
||||||
// SharedGroup
|
|
||||||
void detatch();
|
|
||||||
|
|
||||||
Realm& get_realm() { return *m_target_results->get_realm(); }
|
|
||||||
// Get the version of the current handover object
|
|
||||||
SharedGroup::VersionID version() const noexcept { return m_sg_version; }
|
|
||||||
|
|
||||||
bool is_alive() const noexcept;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Run/rerun the query if needed
|
||||||
|
void run() override;
|
||||||
|
// Prepare the handover object if run() did update the TableView
|
||||||
|
bool 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 release_data() noexcept override;
|
||||||
|
void do_attach_to(SharedGroup& sg) override;
|
||||||
|
void do_detach_from(SharedGroup& sg) override;
|
||||||
|
|
||||||
// Target Results to update and a mutex which guards it
|
// Target Results to update and a mutex which guards it
|
||||||
mutable std::mutex m_target_mutex;
|
mutable std::mutex m_target_mutex;
|
||||||
Results* m_target_results;
|
Results* m_target_results;
|
||||||
|
|
||||||
std::shared_ptr<Realm> m_realm;
|
|
||||||
const SortOrder m_sort;
|
const SortOrder m_sort;
|
||||||
const std::thread::id m_thread_id = std::this_thread::get_id();
|
|
||||||
|
|
||||||
// The source Query, in handover form iff m_sg is null
|
// The source Query, in handover form iff m_sg is null
|
||||||
std::unique_ptr<SharedGroup::Handover<Query>> m_query_handover;
|
std::unique_ptr<SharedGroup::Handover<Query>> m_query_handover;
|
||||||
|
@ -80,40 +63,11 @@ private:
|
||||||
// the query was (re)run since the last time the handover object was created
|
// the query was (re)run since the last time the handover object was created
|
||||||
TableView m_tv;
|
TableView m_tv;
|
||||||
std::unique_ptr<SharedGroup::Handover<TableView>> m_tv_handover;
|
std::unique_ptr<SharedGroup::Handover<TableView>> m_tv_handover;
|
||||||
SharedGroup::VersionID m_sg_version;
|
|
||||||
std::exception_ptr m_error;
|
|
||||||
|
|
||||||
struct Callback {
|
|
||||||
std::function<void (std::exception_ptr)> fn;
|
|
||||||
size_t token;
|
|
||||||
uint_fast64_t delivered_version;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Currently registered callbacks and a mutex which must always be held
|
|
||||||
// while doing anything with them or m_callback_index
|
|
||||||
std::mutex m_callback_mutex;
|
|
||||||
std::vector<Callback> m_callbacks;
|
|
||||||
|
|
||||||
SharedGroup* m_sg = nullptr;
|
|
||||||
|
|
||||||
uint_fast64_t m_handed_over_table_version = -1;
|
uint_fast64_t m_handed_over_table_version = -1;
|
||||||
uint_fast64_t m_delivered_table_version = -1;
|
bool m_did_change = false;
|
||||||
|
|
||||||
// Iteration variable for looping over callbacks
|
|
||||||
// remove_callback() updates this when needed
|
|
||||||
size_t m_callback_index = npos;
|
|
||||||
|
|
||||||
bool m_initial_run_complete = false;
|
bool m_initial_run_complete = false;
|
||||||
|
|
||||||
// Cached value for if m_callbacks is empty, needed to avoid deadlocks in
|
|
||||||
// run() due to lock-order inversion between m_callback_mutex and m_target_mutex
|
|
||||||
// It's okay if this value is stale as at worst it'll result in us doing
|
|
||||||
// some extra work.
|
|
||||||
std::atomic<bool> m_have_callbacks = {false};
|
|
||||||
|
|
||||||
bool is_for_current_thread() const { return m_thread_id == std::this_thread::get_id(); }
|
|
||||||
|
|
||||||
std::function<void (std::exception_ptr)> next_callback();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace _impl
|
} // namespace _impl
|
||||||
|
|
|
@ -0,0 +1,179 @@
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include "impl/background_collection.hpp"
|
||||||
|
|
||||||
|
#include "impl/realm_coordinator.hpp"
|
||||||
|
#include "shared_realm.hpp"
|
||||||
|
|
||||||
|
using namespace realm;
|
||||||
|
using namespace realm::_impl;
|
||||||
|
|
||||||
|
BackgroundCollection::BackgroundCollection(std::shared_ptr<Realm> realm)
|
||||||
|
: m_realm(std::move(realm))
|
||||||
|
, m_sg_version(Realm::Internal::get_shared_group(*m_realm).get_version_of_current_transaction())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t BackgroundCollection::add_callback(CollectionChangeCallback callback)
|
||||||
|
{
|
||||||
|
m_realm->verify_thread();
|
||||||
|
|
||||||
|
auto next_token = [=] {
|
||||||
|
size_t token = 0;
|
||||||
|
for (auto& callback : m_callbacks) {
|
||||||
|
if (token <= callback.token) {
|
||||||
|
token = callback.token + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(m_callback_mutex);
|
||||||
|
auto token = next_token();
|
||||||
|
m_callbacks.push_back({std::move(callback), token, -1ULL});
|
||||||
|
if (m_callback_index == npos) { // Don't need to wake up if we're already sending notifications
|
||||||
|
Realm::Internal::get_coordinator(*m_realm).send_commit_notifications();
|
||||||
|
m_have_callbacks = true;
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BackgroundCollection::remove_callback(size_t token)
|
||||||
|
{
|
||||||
|
Callback old;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_callback_mutex);
|
||||||
|
REALM_ASSERT(m_error || m_callbacks.size() > 0);
|
||||||
|
|
||||||
|
auto it = find_if(begin(m_callbacks), end(m_callbacks),
|
||||||
|
[=](const auto& c) { return c.token == token; });
|
||||||
|
// We should only fail to find the callback if it was removed due to an error
|
||||||
|
REALM_ASSERT(m_error || it != end(m_callbacks));
|
||||||
|
if (it == end(m_callbacks)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t idx = distance(begin(m_callbacks), it);
|
||||||
|
if (m_callback_index != npos && m_callback_index >= idx) {
|
||||||
|
--m_callback_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
old = std::move(*it);
|
||||||
|
m_callbacks.erase(it);
|
||||||
|
|
||||||
|
m_have_callbacks = !m_callbacks.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BackgroundCollection::unregister() noexcept
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_realm_mutex);
|
||||||
|
m_realm = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BackgroundCollection::is_alive() const noexcept
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_realm_mutex);
|
||||||
|
return m_realm != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BackgroundCollection::prepare_handover()
|
||||||
|
{
|
||||||
|
REALM_ASSERT(m_sg);
|
||||||
|
m_sg_version = m_sg->get_version_of_current_transaction();
|
||||||
|
if (do_prepare_handover(*m_sg))
|
||||||
|
++m_results_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BackgroundCollection::deliver(SharedGroup& sg,
|
||||||
|
std::exception_ptr err)
|
||||||
|
{
|
||||||
|
if (!is_for_current_thread()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
m_error = err;
|
||||||
|
return have_callbacks();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto realm_sg_version = sg.get_version_of_current_transaction();
|
||||||
|
if (version() != realm_sg_version) {
|
||||||
|
// Realm version can be newer if a commit was made on our thread or the
|
||||||
|
// user manually called refresh(), or older if a commit was made on a
|
||||||
|
// different thread and we ran *really* fast in between the check for
|
||||||
|
// if the shared group has changed and when we pick up async results
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return do_deliver(sg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BackgroundCollection::call_callbacks()
|
||||||
|
{
|
||||||
|
while (auto fn = next_callback()) {
|
||||||
|
fn(m_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_error) {
|
||||||
|
// Remove all the callbacks as we never need to call anything ever again
|
||||||
|
// after delivering an error
|
||||||
|
std::lock_guard<std::mutex> callback_lock(m_callback_mutex);
|
||||||
|
m_callbacks.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectionChangeCallback BackgroundCollection::next_callback()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> callback_lock(m_callback_mutex);
|
||||||
|
for (++m_callback_index; m_callback_index < m_callbacks.size(); ++m_callback_index) {
|
||||||
|
auto& callback = m_callbacks[m_callback_index];
|
||||||
|
if (m_error || callback.delivered_version != m_results_version) {
|
||||||
|
callback.delivered_version = m_results_version;
|
||||||
|
return callback.fn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_callback_index = npos;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BackgroundCollection::attach_to(SharedGroup& sg)
|
||||||
|
{
|
||||||
|
REALM_ASSERT(!m_sg);
|
||||||
|
|
||||||
|
m_sg = &sg;
|
||||||
|
do_attach_to(sg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BackgroundCollection::detach()
|
||||||
|
{
|
||||||
|
REALM_ASSERT(m_sg);
|
||||||
|
do_detach_from(*m_sg);
|
||||||
|
m_sg = nullptr;
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#ifndef REALM_BACKGROUND_COLLECTION_HPP
|
||||||
|
#define REALM_BACKGROUND_COLLECTION_HPP
|
||||||
|
|
||||||
|
#include <realm/group_shared.hpp>
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <functional>
|
||||||
|
#include <set>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace realm {
|
||||||
|
class Realm;
|
||||||
|
|
||||||
|
using CollectionChangeCallback = std::function<void (std::exception_ptr)>;
|
||||||
|
|
||||||
|
namespace _impl {
|
||||||
|
class BackgroundCollection {
|
||||||
|
public:
|
||||||
|
BackgroundCollection(std::shared_ptr<Realm>);
|
||||||
|
virtual ~BackgroundCollection();
|
||||||
|
void unregister() noexcept;
|
||||||
|
|
||||||
|
virtual void release_data() noexcept = 0;
|
||||||
|
|
||||||
|
size_t add_callback(CollectionChangeCallback callback);
|
||||||
|
void remove_callback(size_t token);
|
||||||
|
|
||||||
|
void call_callbacks();
|
||||||
|
|
||||||
|
bool is_alive() const noexcept;
|
||||||
|
|
||||||
|
Realm& get_realm() const noexcept { return *m_realm; }
|
||||||
|
|
||||||
|
// Attach the handed-over query to `sg`
|
||||||
|
void attach_to(SharedGroup& sg);
|
||||||
|
// Create a new query handover object and stop using the previously attached
|
||||||
|
// SharedGroup
|
||||||
|
void detach();
|
||||||
|
|
||||||
|
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; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual void do_attach_to(SharedGroup&) = 0;
|
||||||
|
virtual void do_detach_from(SharedGroup&) = 0;
|
||||||
|
virtual bool do_prepare_handover(SharedGroup&) = 0;
|
||||||
|
virtual bool do_deliver(SharedGroup&) = 0;
|
||||||
|
|
||||||
|
const std::thread::id m_thread_id = std::this_thread::get_id();
|
||||||
|
bool is_for_current_thread() const { return m_thread_id == std::this_thread::get_id(); }
|
||||||
|
|
||||||
|
mutable std::mutex m_realm_mutex;
|
||||||
|
std::shared_ptr<Realm> m_realm;
|
||||||
|
|
||||||
|
SharedGroup::VersionID m_sg_version;
|
||||||
|
SharedGroup* m_sg = nullptr;
|
||||||
|
|
||||||
|
std::exception_ptr m_error;
|
||||||
|
|
||||||
|
uint_fast64_t m_results_version = 0;
|
||||||
|
|
||||||
|
struct Callback {
|
||||||
|
CollectionChangeCallback fn;
|
||||||
|
size_t token;
|
||||||
|
uint_fast64_t delivered_version;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Currently registered callbacks and a mutex which must always be held
|
||||||
|
// while doing anything with them or m_callback_index
|
||||||
|
std::mutex m_callback_mutex;
|
||||||
|
std::vector<Callback> m_callbacks;
|
||||||
|
|
||||||
|
// Cached value for if m_callbacks is empty, needed to avoid deadlocks in
|
||||||
|
// run() due to lock-order inversion between m_callback_mutex and m_target_mutex
|
||||||
|
// It's okay if this value is stale as at worst it'll result in us doing
|
||||||
|
// some extra work.
|
||||||
|
std::atomic<bool> m_have_callbacks = {false};
|
||||||
|
|
||||||
|
// Iteration variable for looping over callbacks
|
||||||
|
// remove_callback() updates this when needed
|
||||||
|
size_t m_callback_index = npos;
|
||||||
|
|
||||||
|
CollectionChangeCallback next_callback();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace _impl
|
||||||
|
} // namespace realm
|
||||||
|
|
||||||
|
#endif /* REALM_BACKGROUND_COLLECTION_HPP */
|
|
@ -268,7 +268,7 @@ void RealmCoordinator::clean_up_dead_queries()
|
||||||
|
|
||||||
// Ensure the query is destroyed here even if there's lingering refs
|
// Ensure the query is destroyed here even if there's lingering refs
|
||||||
// to the async query elsewhere
|
// to the async query elsewhere
|
||||||
container[i]->release_query();
|
container[i]->release_data();
|
||||||
|
|
||||||
if (container.size() > i + 1)
|
if (container.size() > i + 1)
|
||||||
container[i] = std::move(container.back());
|
container[i] = std::move(container.back());
|
||||||
|
@ -399,7 +399,7 @@ void RealmCoordinator::advance_helper_shared_group_to_latest()
|
||||||
|
|
||||||
// Transfer all new queries over to the main SG
|
// Transfer all new queries over to the main SG
|
||||||
for (auto& query : m_new_queries) {
|
for (auto& query : m_new_queries) {
|
||||||
query->detatch();
|
query->detach();
|
||||||
query->attach_to(*m_query_sg);
|
query->attach_to(*m_query_sg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,9 +32,9 @@ class SharedGroup;
|
||||||
struct AsyncQueryCancelationToken;
|
struct AsyncQueryCancelationToken;
|
||||||
|
|
||||||
namespace _impl {
|
namespace _impl {
|
||||||
class AsyncQuery;
|
class BackgroundCollection;
|
||||||
class WeakRealmNotifier;
|
|
||||||
class ExternalCommitHelper;
|
class ExternalCommitHelper;
|
||||||
|
class WeakRealmNotifier;
|
||||||
|
|
||||||
// RealmCoordinator manages the weak cache of Realm instances and communication
|
// RealmCoordinator manages the weak cache of Realm instances and communication
|
||||||
// between per-thread Realm instances for a given file
|
// between per-thread Realm instances for a given file
|
||||||
|
@ -97,8 +97,8 @@ private:
|
||||||
std::vector<WeakRealmNotifier> m_weak_realm_notifiers;
|
std::vector<WeakRealmNotifier> m_weak_realm_notifiers;
|
||||||
|
|
||||||
std::mutex m_query_mutex;
|
std::mutex m_query_mutex;
|
||||||
std::vector<std::shared_ptr<_impl::AsyncQuery>> m_new_queries;
|
std::vector<std::shared_ptr<_impl::BackgroundCollection>> m_new_queries;
|
||||||
std::vector<std::shared_ptr<_impl::AsyncQuery>> m_queries;
|
std::vector<std::shared_ptr<_impl::BackgroundCollection>> m_queries;
|
||||||
|
|
||||||
// SharedGroup used for actually running async queries
|
// SharedGroup used for actually running async queries
|
||||||
// Will have a read transaction iff m_queries is non-empty
|
// Will have a read transaction iff m_queries is non-empty
|
||||||
|
|
|
@ -42,6 +42,7 @@ namespace realm {
|
||||||
|
|
||||||
namespace _impl {
|
namespace _impl {
|
||||||
class AsyncQuery;
|
class AsyncQuery;
|
||||||
|
class BackgroundCollection;
|
||||||
class RealmCoordinator;
|
class RealmCoordinator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,15 +145,16 @@ namespace realm {
|
||||||
// without making it public to everyone
|
// without making it public to everyone
|
||||||
class Internal {
|
class Internal {
|
||||||
friend class _impl::AsyncQuery;
|
friend class _impl::AsyncQuery;
|
||||||
|
friend class _impl::BackgroundCollection;
|
||||||
friend class _impl::RealmCoordinator;
|
friend class _impl::RealmCoordinator;
|
||||||
|
|
||||||
// AsyncQuery needs access to the SharedGroup to be able to call the
|
// AsyncQuery needs access to the SharedGroup to be able to call the
|
||||||
// handover functions, which are not very wrappable
|
// handover functions, which are not very wrappable
|
||||||
static SharedGroup& get_shared_group(Realm& realm) { return *realm.m_shared_group; }
|
static SharedGroup& get_shared_group(Realm& realm) { return *realm.m_shared_group; }
|
||||||
|
|
||||||
// AsyncQuery needs to be able to access the owning coordinator to
|
// BackgroundCollection needs to be able to access the owning
|
||||||
// wake up the worker thread when a callback is added, and
|
// coordinator to wake up the worker thread when a callback is
|
||||||
// coordinators need to be able to get themselves from a Realm
|
// added, and coordinators need to be able to get themselves from a Realm
|
||||||
static _impl::RealmCoordinator& get_coordinator(Realm& realm) { return *realm.m_coordinator; }
|
static _impl::RealmCoordinator& get_coordinator(Realm& realm) { return *realm.m_coordinator; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue