-
Notifications
You must be signed in to change notification settings - Fork 174
RCORE-2192 RCORE-2193 Fix FLX download progress reporting #7870
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
Conversation
39d7751
to
e387595
Compare
Pull Request Test Coverage Report for Build thomas.goyne_459Details
💛 - Coveralls |
@@ -65,6 +65,25 @@ void AsyncOpenTask::start(AsyncOpenCallback callback) | |||
session->revive_if_needed(); | |||
} | |||
|
|||
util::Future<ThreadSafeReference> AsyncOpenTask::start() |
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.
nice
src/realm/sync/client.cpp
Outdated
@@ -801,7 +804,8 @@ void SessionImpl::update_subscription_version_info() | |||
} | |||
|
|||
bool SessionImpl::process_flx_bootstrap_message(const SyncProgress& progress, DownloadBatchState batch_state, | |||
int64_t query_version, const ReceivedChangesets& received_changesets) | |||
int64_t query_version, DownloadableProgress download_progress, |
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.
nit: at this point should we just pass a ref to the DownloadMessage
rather than picking out its members as separate arguments here?
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 do think that's a bit better. Should probably propagate it further, really.
}); | ||
task->start().get(); | ||
|
||
// Since we set the soft byte limit to one byte, we should have received |
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.
where is the soft byte limit getting set?
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.
Stale comment from when I was trying to use qbs_download_changeset_soft_max_byte_size
rather than making the objects big.
e387595
to
0834fda
Compare
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 lgtm, but i'd like @kiburtse's review as well.
@@ -916,8 +922,8 @@ void SessionImpl::process_pending_flx_bootstrap() | |||
batch_state, pending_batch.changesets.size()); | |||
|
|||
history.integrate_server_changesets( | |||
*pending_batch.progress, downloadable_bytes, pending_batch.changesets, new_version, batch_state, logger, | |||
transact, [&](const TransactionRef& tr, util::Span<Changeset> changesets_applied) { | |||
*pending_batch.progress, 1.0, pending_batch.changesets, new_version, batch_state, logger, transact, |
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.
Is this hardcoded to 1.0
so the progress doesn't change (e.g., go back to 0) while the bootstrap messages are being integrated?
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.
No, it wouldn't change regardless. This was always a hardcoded value, but it used to be a hardcoded value declared sufficiently far from the point of use that it wasn't obviously such.
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.
Unfortunately this is not fully resolved the issue - general case for reconnect and catch up still reports only 1.0 for progress
Since we got this wrong so many times, i think, we have to check in tests actual values for the progress. Precise value of the estimate is not under our control here, but may be we can at least pass raw value from the download message through debug hook and verify that this value propagates through the implementation and is seen in objstore callback in right order?
@@ -1701,28 +1708,28 @@ void SessionWrapper::report_progress(ReportedProgress& p, DownloadableProgress d | |||
upload_estimate = calculate_progress(p.uploaded, p.uploadable, m_final_uploaded); | |||
|
|||
bool download_completed = p.downloaded == 0; | |||
double download_estimate = 1.00; | |||
p.download_estimate = 1.00; |
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.
that check below should be also fixed to take into account initial history scan case where changes are applied directly without hitting bootstrap store first
i think it should just check that this is the flx connection and set estimate unconditionally since it's always provided
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 catch. I wrote up a quick test case that demonstrates the issue:
SECTION("history scan") {
config.sync_config->subscription_initializer = [](const std::shared_ptr<Realm>& realm) {
subscribe_to_all(*realm);
};
// Open a realm and bootstrap it so that we are getting progress notifications for history
// scans rather than bootstraps.
{
successfully_async_open_realm(config);
}
// In another realm, add some more objects to the server that will need to be downloaded
// in a bunch of changesets.
harness->do_with_new_realm([](const std::shared_ptr<Realm>& realm) {
const size_t padding_size = 1024 * 1024;
auto buffer = std::make_unique<char[]>(padding_size);
auto table = realm->read_group().get_table("class_object");
subscribe_to_all(*realm);
for (int i = 5; i < 10; ++i) {
realm->begin_transaction();
auto obj = table->create_object_with_primary_key(ObjectId::gen());
obj.set("int", i);
// ensure that each object is large enough that it'll be sent in
// a separate DOWNLOAD message
obj.set("padding", BinaryData(buffer.get(), padding_size));
realm->commit_transaction();
}
wait_for_upload(*realm);
});
// Re-open the original realm and wait for it to download everything. We should see
// a new batch of progress esimates that are also monotonically increasing.
auto task = Realm::get_synchronized_realm(config);
std::vector<double> estimates;
task->register_download_progress_notifier([&](uint64_t, uint64_t, double estimate) {
// Note that no locking is needed here despite this being called on
// a background thread as the test provides the required synchronization.
// If tsan complains about this, it indicates that the notifier is
// being called at a time that it shouldn't be and there's a bug.
estimates.push_back(estimate);
});
task->start().get();
REQUIRE(estimates.size() >= 5);
REQUIRE_THAT(estimates, EstimatesAreValid());
}
src/realm/sync/client.cpp
Outdated
if (m_flx_pending_bootstrap_store) { | ||
if (m_flx_pending_bootstrap_store->has_pending()) { | ||
download_estimate = downloadable.as_estimate(); | ||
p.download_estimate = downloadable.as_estimate(); |
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 if you moved this down to line 1716, right before download_completed gets set, that would solve the issue @kiburtse is calling out above.
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.
Right, obviously we can get download progress estimates while in the steady state and not just when bootstrapping.
@@ -1701,28 +1708,28 @@ void SessionWrapper::report_progress(ReportedProgress& p, DownloadableProgress d | |||
upload_estimate = calculate_progress(p.uploaded, p.uploadable, m_final_uploaded); | |||
|
|||
bool download_completed = p.downloaded == 0; | |||
double download_estimate = 1.00; | |||
p.download_estimate = 1.00; |
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 catch. I wrote up a quick test case that demonstrates the issue:
SECTION("history scan") {
config.sync_config->subscription_initializer = [](const std::shared_ptr<Realm>& realm) {
subscribe_to_all(*realm);
};
// Open a realm and bootstrap it so that we are getting progress notifications for history
// scans rather than bootstraps.
{
successfully_async_open_realm(config);
}
// In another realm, add some more objects to the server that will need to be downloaded
// in a bunch of changesets.
harness->do_with_new_realm([](const std::shared_ptr<Realm>& realm) {
const size_t padding_size = 1024 * 1024;
auto buffer = std::make_unique<char[]>(padding_size);
auto table = realm->read_group().get_table("class_object");
subscribe_to_all(*realm);
for (int i = 5; i < 10; ++i) {
realm->begin_transaction();
auto obj = table->create_object_with_primary_key(ObjectId::gen());
obj.set("int", i);
// ensure that each object is large enough that it'll be sent in
// a separate DOWNLOAD message
obj.set("padding", BinaryData(buffer.get(), padding_size));
realm->commit_transaction();
}
wait_for_upload(*realm);
});
// Re-open the original realm and wait for it to download everything. We should see
// a new batch of progress esimates that are also monotonically increasing.
auto task = Realm::get_synchronized_realm(config);
std::vector<double> estimates;
task->register_download_progress_notifier([&](uint64_t, uint64_t, double estimate) {
// Note that no locking is needed here despite this being called on
// a background thread as the test provides the required synchronization.
// If tsan complains about this, it indicates that the notifier is
// being called at a time that it shouldn't be and there's a bug.
estimates.push_back(estimate);
});
task->start().get();
REQUIRE(estimates.size() >= 5);
REQUIRE_THAT(estimates, EstimatesAreValid());
}
We need to store the download progress for each batch of a bootstrap and not just at the end for it to be useful in any way. The server will sometimes send us DOWNLOAD messages with a non-one estimate followed by a one estimate where the byte-level information is the same (as the final message is empty). When this happens we need to report the download completion to the user, so add the estimate to the fields checked for changes. A subscription change which doesn't actually change what set of objects is in view can result in an empty DOWNLOAD message with no changes other than the query version, and we should report that too.
0834fda
to
3b3daac
Compare
I've added a test for steady-state progress reporting and made it actually work. I don't like the idea of using a debug hook to get the raw value which was reported by the server because the existing debug hooks have already been making it much more difficult to modify the code and they really need to be a last resort for when there's no other way to test something. I did make the consistency checks for the estimates quite a bit more precise, though. It now verifies that the progress goes up by about the same amount each time, which should always be the case since the objects being downloaded are all the same size. |
3b3daac
to
2f37381
Compare
that was essentially the problem, we really need better checks, no? |
Oh, I removed the monotonic check while cleaning up the test and that wasn't actually redundant. We also can put an upper bound on what progress the first estimate can have. |
2f37381
to
180752e
Compare
180752e
to
4a90688
Compare
I have updated the test to actually work:
|
We need to store the download progress for each batch of a bootstrap and not just at the end for it to be useful in any way. Whoops.
The server will sometimes send us DOWNLOAD messages with a non-one estimate followed by a one estimate where the byte-level information is the same (as the final message is empty). When this happens we need to report the download completion to the user, so add the estimate to the fields checked for changes.
A subscription change which doesn't actually change what set of objects is in view can result in an empty DOWNLOAD message with no changes other than the query version, and we should report that too.
Fixes #7869. Fixes #7867.