Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix support for ReadOnly ThreadSafeReferences #4517

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

### Fixed
* <How to hit and notice issue? what was the impact?> ([#????](https://github.com/realm/realm-core/issues/????), since v?.?.?)
* None.
* A read-only Realm does not allow to create a `ThreadSafeReference` ([Cocoa #5475](https://github.com/realm/realm-cocoa/issues/5475)).

### Breaking changes
* None.
Expand Down
23 changes: 11 additions & 12 deletions src/realm/db.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1701,7 +1701,7 @@ void DB::do_async_commits()
}

bool is_same;
ReadLockInfo next_read_lock = m_read_lock;
Group::ReadLockInfo next_read_lock = m_read_lock;
{
// detect if we're the last "client", and if so, shutdown (must be under lock):
std::lock_guard<InterprocessMutex> lock2(m_writemutex);
Expand Down Expand Up @@ -1859,7 +1859,7 @@ void DB::upgrade_file_format(bool allow_file_format_upgrade, int target_file_for
}


void DB::release_read_lock(ReadLockInfo& read_lock) noexcept
void DB::release_read_lock(Group::ReadLockInfo& read_lock) noexcept
{
std::lock_guard<std::recursive_mutex> lock(m_mutex);
bool found_match = false;
Expand All @@ -1885,7 +1885,7 @@ void DB::release_read_lock(ReadLockInfo& read_lock) noexcept
}


void DB::grab_read_lock(ReadLockInfo& read_lock, VersionID version_id)
void DB::grab_read_lock(Group::ReadLockInfo& read_lock, VersionID version_id)
{
std::lock_guard<std::recursive_mutex> lock(m_mutex);
REALM_ASSERT_RELEASE(is_attached());
Expand Down Expand Up @@ -2114,7 +2114,7 @@ VersionID Transaction::commit_and_continue_as_read()
// we know for certain that the read lock we will grab WILL refer to our own newly
// completed commit.

DB::ReadLockInfo new_read_lock;
Group::ReadLockInfo new_read_lock;
VersionID version_id = VersionID(); // Latest available snapshot
// Grabbing the new lock before releasing the old one prevents m_transaction_count
// from going shortly to zero
Expand Down Expand Up @@ -2355,7 +2355,7 @@ TransactionRef DB::start_read(VersionID version_id)
{
if (!is_attached())
throw LogicError(LogicError::wrong_transact_state);
ReadLockInfo read_lock;
Group::ReadLockInfo read_lock;
grab_read_lock(read_lock, version_id);
ReadLockGuard g(*this, read_lock);
Transaction* tr = new Transaction(shared_from_this(), &m_alloc, read_lock, DB::transact_Reading);
Expand All @@ -2368,7 +2368,7 @@ TransactionRef DB::start_frozen(VersionID version_id)
{
if (!is_attached())
throw LogicError(LogicError::wrong_transact_state);
ReadLockInfo read_lock;
Group::ReadLockInfo read_lock;
grab_read_lock(read_lock, version_id);
ReadLockGuard g(*this, read_lock);
Transaction* tr = new Transaction(shared_from_this(), &m_alloc, read_lock, DB::transact_Frozen);
Expand All @@ -2377,10 +2377,9 @@ TransactionRef DB::start_frozen(VersionID version_id)
return TransactionRef(tr, TransactionDeleter);
}

Transaction::Transaction(DBRef _db, SlabAlloc* alloc, DB::ReadLockInfo& rli, DB::TransactStage stage)
: Group(alloc)
Transaction::Transaction(DBRef _db, SlabAlloc* alloc, Group::ReadLockInfo& rli, DB::TransactStage stage)
: Group(alloc, rli)
, db(_db)
, m_read_lock(rli)
{
bool writable = stage == DB::transact_Writing;
m_transact_stage = DB::transact_Ready;
Expand Down Expand Up @@ -2510,7 +2509,7 @@ DB::version_type Transaction::commit()
// To set it, we grab a readlock on the latest available snapshot
// and release it again.
VersionID version_id = VersionID(); // Latest available snapshot
DB::ReadLockInfo lock_after_commit;
Group::ReadLockInfo lock_after_commit;
db->grab_read_lock(lock_after_commit, version_id);
db->release_read_lock(lock_after_commit);

Expand Down Expand Up @@ -2540,7 +2539,7 @@ void Transaction::commit_and_continue_writing()
// To set it, we grab a readlock on the latest available snapshot
// and release it again.
VersionID version_id = VersionID(); // Latest available snapshot
DB::ReadLockInfo lock_after_commit;
Group::ReadLockInfo lock_after_commit;
db->grab_read_lock(lock_after_commit, version_id);
db->release_read_lock(m_read_lock);
m_read_lock = lock_after_commit;
Expand Down Expand Up @@ -2592,7 +2591,7 @@ TransactionRef DB::start_write(bool nonblocking)
}
m_write_transaction_open = true;
}
ReadLockInfo read_lock;
Group::ReadLockInfo read_lock;
Transaction* tr;
try {
grab_read_lock(read_lock, VersionID());
Expand Down
25 changes: 10 additions & 15 deletions src/realm/db.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -376,20 +376,14 @@ class DB : public std::enable_shared_from_this<DB> {
Replication* m_replication = nullptr;
struct SharedInfo;
struct ReadCount;
struct ReadLockInfo {
uint_fast64_t m_version = std::numeric_limits<version_type>::max();
uint_fast32_t m_reader_idx = 0;
ref_type m_top_ref = 0;
size_t m_file_size = 0;
};
class ReadLockGuard;

// Member variables
size_t m_free_space = 0;
size_t m_locked_space = 0;
size_t m_used_space = 0;
uint_fast32_t m_local_max_entry = 0; // highest version observed by this DB
std::vector<ReadLockInfo> m_local_locks_held; // tracks all read locks held by this DB
std::vector<Group::ReadLockInfo> m_local_locks_held; // tracks all read locks held by this DB
util::File m_file;
util::File::Map<SharedInfo> m_file_map; // Never remapped, provides access to everything but the ringbuffer
util::File::Map<SharedInfo> m_reader_map; // provides access to ringbuffer, remapped as needed when it grows
Expand Down Expand Up @@ -481,11 +475,11 @@ class DB : public std::enable_shared_from_this<DB> {
///
/// As a side effect update memory mapping to ensure that the ringbuffer
/// entries referenced in the readlock info is accessible.
void grab_read_lock(ReadLockInfo&, VersionID);
void grab_read_lock(Group::ReadLockInfo&, VersionID);

// Release a specific read lock. The read lock MUST have been obtained by a
// call to grab_read_lock().
void release_read_lock(ReadLockInfo&) noexcept;
void release_read_lock(Group::ReadLockInfo&) noexcept;

// Release all read locks held by this DB object. After release, further calls to
// release_read_lock for locks already released must be avoided.
Expand Down Expand Up @@ -533,10 +527,12 @@ inline void DB::get_stats(size_t& free_space, size_t& used_space, util::Optional
}
}


// A `Transaction` is used when reading from and writing to a realm.
// One important thing to keep in mind about the difference between `Group` and `Transaction` is that read-only realms
// do not and cannot use a `Transaction`. Read-only realms just use a `Group` to access data from a realm.
class Transaction : public Group {
public:
Transaction(DBRef _db, SlabAlloc* alloc, DB::ReadLockInfo& rli, DB::TransactStage stage);
Transaction(DBRef _db, SlabAlloc* alloc, Group::ReadLockInfo& rli, DB::TransactStage stage);
// convenience, so you don't need to carry a reference to the DB around
~Transaction();

Expand Down Expand Up @@ -640,7 +636,6 @@ class Transaction : public Group {
mutable std::unique_ptr<_impl::History> m_history_read;
mutable _impl::History* m_history = nullptr;

DB::ReadLockInfo m_read_lock;
DB::TransactStage m_transact_stage = DB::transact_Ready;

friend class DB;
Expand Down Expand Up @@ -817,7 +812,7 @@ inline DB::TransactStage Transaction::get_transact_stage() const noexcept

class DB::ReadLockGuard {
public:
ReadLockGuard(DB& shared_group, ReadLockInfo& read_lock) noexcept
ReadLockGuard(DB& shared_group, Group::ReadLockInfo& read_lock) noexcept
: m_shared_group(shared_group)
, m_read_lock(&read_lock)
{
Expand All @@ -834,7 +829,7 @@ class DB::ReadLockGuard {

private:
DB& m_shared_group;
ReadLockInfo* m_read_lock;
Group::ReadLockInfo* m_read_lock;
};

template <class O>
Expand Down Expand Up @@ -946,7 +941,7 @@ inline void Transaction::rollback_and_continue_as_read(O* observer)
template <class O>
inline bool Transaction::internal_advance_read(O* observer, VersionID version_id, _impl::History& hist, bool writable)
{
DB::ReadLockInfo new_read_lock;
Group::ReadLockInfo new_read_lock;
db->grab_read_lock(new_read_lock, version_id); // Throws
REALM_ASSERT(new_read_lock.m_version >= m_read_lock.m_version);
if (new_read_lock.m_version == m_read_lock.m_version) {
Expand Down
12 changes: 12 additions & 0 deletions src/realm/group.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,18 @@ Group::Group(SlabAlloc* alloc) noexcept
init_array_parents();
}

Group::Group(SlabAlloc* alloc, ReadLockInfo read_lock_info) noexcept
: m_read_lock(read_lock_info)
, m_alloc(*alloc) // Throws
, m_top(m_alloc)
, m_tables(m_alloc)
, m_table_names(m_alloc)
, m_is_shared(true)
, m_total_rows(0)
{
init_array_parents();
}


Group::TableRecycler Group::g_table_recycler_1;
Group::TableRecycler Group::g_table_recycler_2;
Expand Down
22 changes: 21 additions & 1 deletion src/realm/group.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include <realm/metrics/metrics.hpp>
#include <realm/table.hpp>
#include <realm/alloc_slab.hpp>
#include <realm/version_id.hpp>

namespace realm {

Expand All @@ -46,7 +47,9 @@ class TransactLogParser;
}


/// A group is a collection of named tables.
/// A group is a collection of named tables and meta data for them.
/// It is the topmost object in the tree and keeps track of and caches (parts of) the top of the tree with all the
/// data.
///
class Group : public ArrayParent {
public:
Expand Down Expand Up @@ -243,6 +246,9 @@ class Group : public ArrayParent {
/// Returns the keys for all tables in this group.
TableKeys get_table_keys() const;

// Returns a new `VersionID` using the `m_read_lock`.
VersionID get_current_version() const;

/// \defgroup group_table_access Table Accessors
///
/// has_table() returns true if, and only if this group contains a table
Expand Down Expand Up @@ -559,6 +565,14 @@ class Group : public ArrayParent {
}
#endif

struct ReadLockInfo {
uint_fast64_t m_version = std::numeric_limits<VersionID::version_type>::max();
uint_fast32_t m_reader_idx = 0;
ref_type m_top_ref = 0;
size_t m_file_size = 0;
};
ReadLockInfo m_read_lock;

protected:
virtual Replication* const* get_repl() const
{
Expand Down Expand Up @@ -676,6 +690,7 @@ class Group : public ArrayParent {
Group(shared_tag) noexcept;

Group(SlabAlloc* alloc) noexcept;
Group(SlabAlloc* alloc, ReadLockInfo read_lock_info) noexcept;
void init_array_parents() noexcept;

void open(ref_type top_ref, const std::string& file_path);
Expand Down Expand Up @@ -938,6 +953,11 @@ inline TableKeys Group::get_table_keys() const
return TableKeys(this);
}

inline VersionID Group::get_current_version() const
{
return VersionID(m_read_lock.m_version, m_read_lock.m_reader_idx);
}

inline bool Group::is_attached() const noexcept
{
return m_attached;
Expand Down
56 changes: 43 additions & 13 deletions src/realm/object-store/shared_realm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,16 @@ std::shared_ptr<Transaction> Realm::transaction_ref()

std::shared_ptr<Transaction> Realm::duplicate() const
{
return std::static_pointer_cast<Transaction>(m_coordinator->begin_read(read_transaction_version(), is_frozen()));
VersionID version;
if (m_config.immutable()) {
version = get_current_version();
}
else {
version = read_transaction_version();
}
std::shared_ptr<Group> group = m_coordinator->begin_read(version, is_frozen());
std::shared_ptr<Transaction> transaction = std::static_pointer_cast<Transaction>(group);
return transaction;
}

std::shared_ptr<DB>& Realm::Internal::get_db(Realm& realm)
Expand Down Expand Up @@ -551,37 +560,58 @@ bool Realm::verify_notifications_available(bool throw_on_error) const
return true;
}

VersionID Realm::get_current_version() const
{
verify_thread();
verify_open();
if (!m_group) {
throw InvalidTransactionException("Cannot retrieve a version without a group.");
}
return m_group->get_current_version();
}

VersionID Realm::read_transaction_version() const
{
verify_thread();
verify_open();
check_can_create_any_transaction(this);
if (!is_in_read_transaction()) {
throw InvalidTransactionException("Cannot retrieve a transaction version without a transaction.");
}
return static_cast<Transaction&>(*m_group).get_version_of_current_transaction();
}

util::Optional<VersionID> Realm::current_transaction_version() const
{
util::Optional<VersionID> current_transaction_version;
if (m_group) {
current_transaction_version = read_transaction_version();
}
else if (m_frozen_version) {
current_transaction_version = m_frozen_version;
}
return current_transaction_version;
}

uint_fast64_t Realm::get_number_of_versions() const
{
verify_open();
check_can_create_any_transaction(this);
return m_coordinator->get_number_of_versions();
}

bool Realm::is_in_read_transaction() const noexcept
{
return !m_config.immutable() && !is_closed() && m_group;
}

bool Realm::is_in_transaction() const noexcept
{
return !m_config.immutable() && !is_closed() && m_group &&
transaction().get_transact_stage() == DB::transact_Writing;
return is_in_read_transaction() && transaction().get_transact_stage() == DB::transact_Writing;
}

util::Optional<VersionID> Realm::current_transaction_version() const
bool Realm::has_group() const noexcept
{
util::Optional<VersionID> ret;
if (m_group) {
ret = static_cast<Transaction&>(*m_group).get_version_of_current_transaction();
}
else if (m_frozen_version) {
ret = m_frozen_version;
}
return ret;
return m_group != nullptr;
}

void Realm::enable_wait_for_change()
Expand Down
Loading