-
Notifications
You must be signed in to change notification settings - Fork 164
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
Simplify internal commit notification #7031
Conversation
1900351
to
4091ad5
Compare
Pull Request Test Coverage Report for Build github_pull_request_279324
💛 - Coveralls |
747823f
to
24f055f
Compare
// Ensure the notifiers aren't holding on to Transactions after we destroy | ||
// the History object the DB depends on | ||
// If there's any active NotificationTokens they'll keep the notifiers alive, | ||
// so tell the notifiers to release their Transactions so that the DB can | ||
// be closed immediately. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment was stale and the reason why we originally needed release_data() no longer applies, but it is still required for other reasons.
// In general, `m_upload_target_version` follows `m_last_version_available` | ||
// as it is increased, but in some cases, `m_upload_target_version` will be | ||
// kept fixed for a while in order to constrain the uploading process. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this was once true, but now the only time m_upload_target_version wasn't the same as m_last_version_available was at times where we couldn't be uploading changesets anyway (such as while in the process of applying a client reset recovery on the sync worker thread).
if (!m_pending_flx_sub_set || m_pending_flx_sub_set->snapshot_version < m_upload_progress.client_version) { | ||
m_pending_flx_sub_set = get_flx_subscription_store()->get_next_pending_version( | ||
m_last_sent_flx_query_version, m_upload_progress.client_version); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code was dead: the single caller of send_upload_message() (send_message()) ensures m_pending_flx_sub_set up to date as part of deciding if it should call send_upload_message() in the first place, so it never needs to be refreshed here.
@@ -231,25 +219,24 @@ TEST(ClientReset_InitialLocalChanges) | |||
ClientServerFixture fixture(dir, test_context); | |||
fixture.start(); | |||
|
|||
Session session_1 = fixture.make_session(path_1, server_path); | |||
DBRef db_1 = DB::create(make_client_replication(), path_1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test predates core 6 and was doing something which is now quite weird (writing to a Realm via a second DB not linked to the sync session and not via a RealmCoordinator). It would have continued to work unchanged by continuing to use nonsync_transact_notify(), but since it isn't actually trying to test multiprocess things I made it normal instead.
// NOTE: There was a race condition with `write_transaction_notifying_session` where session_2 | ||
// was completing sync before the write transaction was completed, leading to a | ||
// `realm::TableNameInUse` exception. Broke up this function and moved the call to | ||
// `nonsync_transact_notify()` to after the write transactions. | ||
auto version_1 = perform_write_transaction(db_1, std::move(fn_1)); | ||
auto version_2 = perform_write_transaction(db_2, std::move(fn_2)); | ||
session_1.nonsync_transact_notify(version_1); | ||
session_2.nonsync_transact_notify(version_2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This race goes away by just moving the writes to before binding.
else { | ||
CHECK_GREATER(progress_version, 0); | ||
CHECK_GREATER(snapshot_version, 3); | ||
switch (entry_1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This more precise test also passes on master (with some nonsync_transact_notify()s added).
@@ -3783,6 +3771,76 @@ TEST(Sync_UploadDownloadProgress_7) | |||
// down the session that is in the process of being created. | |||
} | |||
|
|||
TEST(Sync_UploadProgress_EmptyCommits) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test doesn't pass on master, but I think the new behavior is sensible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There seems to be no issue with the test
30c2109
to
7fb0ce1
Compare
if (m_pending_flx_sub_set && m_pending_flx_sub_set->snapshot_version < m_upload_target_version) { | ||
target_upload_version = m_pending_flx_sub_set->snapshot_version; | ||
} | ||
version_type target_upload_version = get_db()->get_version_of_latest_snapshot(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't the target be m_last_version_available
? Is this because that's actually not the case since subscriptions don't report their snapshot version anymore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAICT there's no reason to limit it to m_last_version_available. If a commit happens on another thread while we're enqueued to send, it's fine to upload that changeset while the notification is still waiting in the event loop's queue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM - Nice, this simplifies some of the coordination around realm updates.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any simplification to the notification system is a win from my perspective 👍
The documentation suggests there was once a mechanism for uploading up to a specific version and then stopping, but this is now only used for sending QUERY messages at the correct time, and that can be done more directly. This cuts down on the amount of state that needs to be tracked and sometimes (very insignificantly) improves upload latency.
The concrete bug which this fixes is that `await realm.subscriptions.update {}; await realm.refresh()` would hang forever. SubscriptionStore writes failed to notify RealmCoordinator of the write, so the async refresh would see that the Realm is not on the latest version, register a handler to be called when the autorefresh happened, and then nothing would ever schedule the autorefresh. The sync client needs to be notified of non-sync writes and notify non-sync components when it performs writes. When it was first written, DB did not exist yet and so this was orchestrated via RealmCoordinator. However, that's a very awkward place to do it: not all writes go via RealmCoordinator, and the lifetime of sync sessions isn't actually tied to a coordinator. Nowadays we do have DB, and handling commit notifications there greatly simplifies everything. There was also a *second* mechanism for notifying the sync client of writes which modified the subscription store. This appears to have been entirely redundant and unnecessary.
5c83687
to
52b4f97
Compare
The concrete bug which this fixes is that
await realm.subscriptions.update {}; await realm.refresh()
would hang forever. SubscriptionStore writes failed to notify RealmCoordinator of the write, so the async refresh would see that the Realm is not on the latest version, register a handler to be called when the autorefresh happened, and then nothing would ever schedule the autorefresh.The sync client needs to be notified of non-sync writes and notify non-sync components when it performs writes. When it was first written, DB did not exist yet and so this was orchestrated via RealmCoordinator. However, that's a very awkward place to do it: not all writes go via RealmCoordinator, and the lifetime of sync sessions isn't actually tied to a coordinator. Nowadays we do have DB, and handling commit notifications there greatly simplifies everything.
There was also a second mechanism for notifying the sync client of writes which modified the subscription store. This appears to have been mostly redundant and unnecessary. The only additional information it conveyed was a number only used in some assertions.
Sync progress notifications somewhat relied on that some of the internal writes by the sync client didn't trigger them, and this change made it so that some very useless notifications were sent. To fix this, I made it so that commits will only trigger notifications if they changed the uploadable bytes, i.e. empty changesets don't produce notifications.
Ideally ExternalCommitHelper would live on DB rather than RealmCoordinator and nonsync_transact_notify() could go away entirely, but that looks like it'd be a pretty complicated change.