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

RCORE-2191 Add support for multi-process subscription state change notifications #7862

Merged
merged 1 commit into from
Jul 30, 2024

Conversation

tgoyne
Copy link
Member

@tgoyne tgoyne commented Jul 4, 2024

As with the other multi-process notifications, the core idea here is to eliminate the in-memory state and produce notifications based entirely on the current state of the Realm file.

SubscriptionStore::update_state() has been replaced with separate functions for the specific legal state transitions, which also take a write transaction as a parameter. These functions are called by PendingBootstrapStore inside the same write transaction as the bootstrap updates which changed the subscription state. This is both a minor performance optimization (due to fewer writes) and eliminates a brief window between the two writes where the Realm file was in an inconsistent state.

There's a minor functional change here: previously old subscription sets were superseded when the new one reached the Completed state, and now they are superseded on AwaitingMark. This aligns it with when the new subscription set becomes the one which is returned by get_active().

Fixes #7861.

@tgoyne tgoyne self-assigned this Jul 4, 2024
@cla-bot cla-bot bot added the cla: yes label Jul 4, 2024
@tgoyne tgoyne force-pushed the tg/subscription-state-notification branch from 9da38f2 to 876436b Compare July 4, 2024 00:55
if (auto status = sess->receive_query_error_message(raw_error_code, message, query_version); !status.is_ok()) {
close_due_to_protocol_error(std::move(status));
if (Session* sess = find_and_validate_session(session_ident, "QUERY_ERROR")) {
sess->receive_query_error_message(raw_error_code, message, query_version);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

receive_query_error_message() only ever returned Status::Ok() the code handling it returning an error was dead.

// Ignore the message if the deactivation process has been initiated,
// because in that case, the associated Realm and SessionWrapper must
// not be accessed any longer.
if (m_state == Active) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking for m_state == Active was done several time along the way so I eliminated all but one.

{
auto tr = m_db->start_read();
auto bootstrap_table = tr->get_table(m_table);
if (bootstrap_table->is_empty()) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

peek_pending() and pop_front_pending() are only called if has_pending() returns true, so the table should never be empty in either of them.

@@ -245,7 +240,7 @@ PendingBootstrapStore::PendingBatch PendingBootstrapStore::peek_pending(size_t l
progress_obj.get<int64_t>(m_progress_download_client_version);
progress.upload.last_integrated_server_version = progress_obj.get<int64_t>(m_progress_upload_server_version);
progress.upload.client_version = progress_obj.get<int64_t>(m_progress_upload_client_version);
ret.progress = std::move(progress);
ret.progress = progress;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SyncProgress is trivially copyable so moving it doesn't do anything.


private:
DBRef m_db;
// The pending_bootstrap_store is tied to the lifetime of a session, so a shared_ptr is not needed
util::Logger& m_logger;
_impl::ClientProtocol m_client_protocol;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been unused for a while.

@@ -548,7 +558,7 @@ SubscriptionSet MutableSubscriptionSet::commit()
const auto flx_version = version();
m_tr->commit_and_continue_as_read();

mgr->process_notifications(m_state, flx_version, std::string_view(error_str()));
mgr->report_progress();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notifications will be sent without this call, but the timing would change and that maybe could be a problem.

CHECK_NOT(store.has_pending());
pending_batch = store.peek_pending(1024);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now an assertion failure rather than returning an empty result.

@tgoyne tgoyne marked this pull request as ready for review July 4, 2024 03:42
@tgoyne tgoyne requested review from jbreams and michael-wb July 4, 2024 03:42
@tgoyne tgoyne force-pushed the tg/subscription-state-notification branch from 876436b to 54a10e7 Compare July 9, 2024 22:50
Comment on lines -1564 to -1579
m_sess->logger.debug("Marking query version %1 as complete after receiving MARK message",
m_flx_pending_mark_version);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to keep this debug message to show when the bootstrap/version is complete. Perhaps move it to be printed out in download_complete().

Comment on lines 348 to 373
// Mark query_version as having begun bootstrapping. This should be called
// inside the write transaction used to store the first set of changesets.
//
// This should only be called internally within the sync client.
void begin_bootstrap(const Transaction&, int64_t query_version);
// Mark query_version as having completed bootstrapping. This should be
// called inside the write transaction which removes the final pending changeset.
//
// This should only be called internally within the sync client.
void complete_bootstrap(const Transaction&, int64_t query_version);
// Roll query_version back to the Pending state if it is currently Bootstrapping.
// Has no effect if the bootstrap in progress is not the first boostrap for
// this subscription set.
//
// This should only be called internally within the sync client.
void update_state(int64_t version_id, SubscriptionSet::State state,
std::optional<std::string_view> error_str = util::none)
REQUIRES(!m_pending_notifications_mutex);
void cancel_bootstrap(const Transaction&, int64_t query_version);
// Report that a download has completed, meaning that the active subscription
// set should advance to the Completed state if it is currently in the
// AwaitingMark state. Has no effect if it is in any other state.
//
// This should only be called internally within the sync client.
void download_complete();
Copy link
Contributor

@michael-wb michael-wb Jul 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment line about server initiated bootstraps for these 4 functions - e.g.,
// Has no effect if this is a server initiated bootstrap for the active query version.

Copy link
Contributor

@michael-wb michael-wb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall the changes look good; just two minor comments
This branch needs to be updated to the latest feature/role-change and I'd prefer to get the feature branch merged to master prior to merging these changes.

Base automatically changed from feature/role-change to master July 19, 2024 00:39
@tgoyne tgoyne force-pushed the tg/subscription-state-notification branch 3 times, most recently from 7d3c8d8 to dd4488c Compare July 21, 2024 02:46
Copy link

coveralls-official bot commented Jul 21, 2024

Pull Request Test Coverage Report for Build thomas.goyne_466

Details

  • 459 of 465 (98.71%) changed or added relevant lines in 13 files are covered.
  • 60 unchanged lines in 18 files lost coverage.
  • Overall coverage increased (+0.03%) to 91.095%

Changes Missing Coverage Covered Lines Changed/Added Lines %
src/realm/sync/subscriptions.cpp 142 148 95.95%
Files with Coverage Reduction New Missed Lines %
src/realm/array_string.cpp 1 88.03%
src/realm/index_string.hpp 1 93.48%
src/realm/list.cpp 1 87.52%
src/realm/query_engine.hpp 1 93.94%
src/realm/query_expression.hpp 1 93.81%
test/fuzz_tester.hpp 1 57.73%
src/realm/array_blobs_big.cpp 2 98.58%
test/test_lang_bind_helper.cpp 2 93.2%
test/test_sync.cpp 2 93.03%
src/realm/sync/client.cpp 3 91.22%
Totals Coverage Status
Change from base Build 2531: 0.03%
Covered Lines: 216809
Relevant Lines: 238003

💛 - Coveralls

@tgoyne tgoyne force-pushed the tg/subscription-state-notification branch from dd4488c to 2f366b5 Compare July 22, 2024 02:01
if (old_state == State::Complete) {
throw RuntimeError(ErrorCodes::SyncProtocolInvariantFailed,
util::format("Received error '%1' for already-completed query version %2. This "
"may be due to a queryable field being removed in the server-side "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this even possible?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if the scenario mentioned in the message is real or if this is just guarding against the server doing something invalid.

@@ -1626,15 +1508,34 @@ void SessionWrapper::check_progress()
REALM_ASSERT(!m_finalized);
REALM_ASSERT(m_sess);

if (!m_progress_handler && m_upload_completion_handlers.empty() && m_sync_completion_handlers.empty())
// Check if there's anything which even wants progress or completion information
bool has_progress_handler = m_progress_handler && m_reliable_download_progress;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a valid reason outside of the unit tests to not have a progress handler at this point?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, m_progress_handler is always set when using SyncSession and it's only null in the sync unit tests.

Copy link
Contributor

@michael-wb michael-wb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

As with the other multi-process notifications, the core idea here is to
eliminate the in-memory state and produce notifications based entirely on the
current state of the Realm file.

SubscriptionStore::update_state() has been replaced with separate functions for
the specific legal state transitions, which also take a write transaction as a
parameter. These functions are called by PendingBootstrapStore inside the same
write transaction as the bootstrap updates which changed the subscription
state. This is both a minor performance optimization (due to fewer writes) and
eliminates a brief window between the two writes where the Realm file was in an
inconsistent state.

There's a minor functional change here: previously old subscription sets were
superseded when the new one reached the Completed state, and now they are
superseded on AwaitingMark. This aligns it with when the new subscription set
becomes the one which is returned by get_active().
@tgoyne tgoyne force-pushed the tg/subscription-state-notification branch from 2f366b5 to dec4408 Compare July 30, 2024 18:04
@tgoyne tgoyne merged commit 819bb98 into master Jul 30, 2024
44 checks passed
@tgoyne tgoyne deleted the tg/subscription-state-notification branch July 30, 2024 19:11
@github-actions github-actions bot mentioned this pull request Aug 1, 2024
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 29, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Make subscription state change notifications multiprocess-compatible
3 participants