p2p: Avoid deadlock with two partially updated devices.
Suppose you have two devices A and B on the same LAN and both devices
are using p2p for updates. Suppose that you turn on A and it starts to
update when the hourly update check kicks in. Then you reboot A in the
middle of, say, the 6th operation. At this point it will share a p2p
file with payload for 5.5 operations (any number between 5 and
6). Update checks are deferred so it's currently not updating.
Then you turn on B and once the update check kicks in it sees that A
has the update. Then B downloads and applies whatever it gets from A
and it ends up sharing a file of the same size (5.5 operations). When
A has nothing more to serve, the connection is dropped and B tries
reconnecting kDownloadP2PMaxRetryCount (5) times before failing the
update check.
Now A wakes up for its update check (we're assuming B had time to get
the bytes from A before A's hourly check kicks in). A tries to use p2p
and since it has already completed 5 operations it asks for a peer
with at least 5 ops in it. Since B qualifies (it's sharing 5.5 ops)
and is the only machine on the LAN, A is downloading from B and then
fails in the same way as B did in the paragraph above.
This results in deadlock with neither of the machines making forward
progress. Fortunately, kMaxP2PAttemptTimeSeconds (= two days) and
kMaxP2PAttempts (= 10) saves us in this case since both A and B will
fall back to downloading without p2p.
This CL fixes this problem by always requesting enough bytes to finish
the current operation.
BUG=chromium:297170
TEST=Unit test that kPrefsManifestDataLength is written to + unit
tests pass. Also did a manual test where I initiated an update and
then rebooted the device in an environment where the payload was
available via p2p. After rebooting and triggering a non-interactive
update check I observed that the --minimum-size value passed to
p2p-client by update_engine (p2p-client invocations are logged in
/var/log/messages), 69575742 bytes, was bigger than the number of
bytes already downloaded (observed by looking at the size of the p2p
file in /var/cache/p2p for the current attempt), 69540111 bytes.
Change-Id: I5e0e63f137ff139daec6ef8f0c83ce9dc76fb2a9
Reviewed-on: https://chromium-review.googlesource.com/170519
Reviewed-by: Alex Deymo <deymo@chromium.org>
Reviewed-by: Chris Sosa <sosa@chromium.org>
Commit-Queue: David Zeuthen <zeuthen@chromium.org>
Tested-by: David Zeuthen <zeuthen@chromium.org>
diff --git a/delta_performer.cc b/delta_performer.cc
index 68161d2..c20614d 100644
--- a/delta_performer.cc
+++ b/delta_performer.cc
@@ -1147,6 +1147,7 @@
if (!quick) {
prefs->SetString(kPrefsUpdateCheckResponseHash, "");
prefs->SetInt64(kPrefsUpdateStateNextDataOffset, -1);
+ prefs->SetInt64(kPrefsUpdateStateNextDataLength, 0);
prefs->SetString(kPrefsUpdateStateSHA256Context, "");
prefs->SetString(kPrefsUpdateStateSignedSHA256Context, "");
prefs->SetString(kPrefsUpdateStateSignatureBlob, "");
@@ -1167,6 +1168,21 @@
TEST_AND_RETURN_FALSE(prefs_->SetInt64(kPrefsUpdateStateNextDataOffset,
buffer_offset_));
last_updated_buffer_offset_ = buffer_offset_;
+
+ if (next_operation_num_ < num_total_operations_) {
+ const bool is_kernel_partition =
+ next_operation_num_ >= num_rootfs_operations_;
+ const DeltaArchiveManifest_InstallOperation &op =
+ is_kernel_partition ?
+ manifest_.kernel_install_operations(
+ next_operation_num_ - num_rootfs_operations_) :
+ manifest_.install_operations(next_operation_num_);
+ TEST_AND_RETURN_FALSE(prefs_->SetInt64(kPrefsUpdateStateNextDataLength,
+ op.data_length()));
+ } else {
+ TEST_AND_RETURN_FALSE(prefs_->SetInt64(kPrefsUpdateStateNextDataLength,
+ 0));
+ }
}
TEST_AND_RETURN_FALSE(prefs_->SetInt64(kPrefsUpdateStateNextOperation,
next_operation_num_));