1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/download/internal/common/parallel_download_job.h"
6
7 #include <algorithm>
8
9 #include "base/bind.h"
10 #include "base/metrics/histogram_macros.h"
11 #include "base/time/time.h"
12 #include "components/download/internal/common/parallel_download_utils.h"
13 #include "components/download/public/common/download_create_info.h"
14 #include "components/download/public/common/download_stats.h"
15 #include "mojo/public/cpp/bindings/pending_remote.h"
16 #include "net/traffic_annotation/network_traffic_annotation.h"
17 #include "net/url_request/referrer_policy.h"
18
19 namespace download {
20 namespace {
21 const int kDownloadJobVerboseLevel = 1;
22 } // namespace
23
ParallelDownloadJob(DownloadItem * download_item,CancelRequestCallback cancel_request_callback,const DownloadCreateInfo & create_info,URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr url_loader_factory_provider,DownloadJobFactory::WakeLockProviderBinder wake_lock_provider_binder)24 ParallelDownloadJob::ParallelDownloadJob(
25 DownloadItem* download_item,
26 CancelRequestCallback cancel_request_callback,
27 const DownloadCreateInfo& create_info,
28 URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr
29 url_loader_factory_provider,
30 DownloadJobFactory::WakeLockProviderBinder wake_lock_provider_binder)
31 : DownloadJobImpl(download_item, std::move(cancel_request_callback), true),
32 initial_request_offset_(create_info.offset),
33 initial_received_slices_(download_item->GetReceivedSlices()),
34 content_length_(create_info.total_bytes),
35 requests_sent_(false),
36 is_canceled_(false),
37 url_loader_factory_provider_(std::move(url_loader_factory_provider)),
38 wake_lock_provider_binder_(std::move(wake_lock_provider_binder)) {}
39
40 ParallelDownloadJob::~ParallelDownloadJob() = default;
41
OnDownloadFileInitialized(DownloadFile::InitializeCallback callback,DownloadInterruptReason result,int64_t bytes_wasted)42 void ParallelDownloadJob::OnDownloadFileInitialized(
43 DownloadFile::InitializeCallback callback,
44 DownloadInterruptReason result,
45 int64_t bytes_wasted) {
46 DownloadJobImpl::OnDownloadFileInitialized(std::move(callback), result,
47 bytes_wasted);
48 if (result == DOWNLOAD_INTERRUPT_REASON_NONE)
49 BuildParallelRequestAfterDelay();
50 }
51
Cancel(bool user_cancel)52 void ParallelDownloadJob::Cancel(bool user_cancel) {
53 is_canceled_ = true;
54 DownloadJobImpl::Cancel(user_cancel);
55
56 if (!requests_sent_) {
57 timer_.Stop();
58 return;
59 }
60
61 for (auto& worker : workers_)
62 worker.second->Cancel(user_cancel);
63 }
64
Pause()65 void ParallelDownloadJob::Pause() {
66 DownloadJobImpl::Pause();
67
68 if (!requests_sent_) {
69 timer_.Stop();
70 return;
71 }
72
73 for (auto& worker : workers_)
74 worker.second->Pause();
75 }
76
Resume(bool resume_request)77 void ParallelDownloadJob::Resume(bool resume_request) {
78 DownloadJobImpl::Resume(resume_request);
79 if (!resume_request)
80 return;
81
82 // Send parallel requests if the download is paused previously.
83 if (!requests_sent_) {
84 if (!timer_.IsRunning())
85 BuildParallelRequestAfterDelay();
86 return;
87 }
88
89 for (auto& worker : workers_)
90 worker.second->Resume();
91 }
92
GetParallelRequestCount() const93 int ParallelDownloadJob::GetParallelRequestCount() const {
94 return GetParallelRequestCountConfig();
95 }
96
GetMinSliceSize() const97 int64_t ParallelDownloadJob::GetMinSliceSize() const {
98 return GetMinSliceSizeConfig();
99 }
100
GetMinRemainingTimeInSeconds() const101 int ParallelDownloadJob::GetMinRemainingTimeInSeconds() const {
102 return GetParallelRequestRemainingTimeConfig().InSeconds();
103 }
104
CancelRequestWithOffset(int64_t offset)105 void ParallelDownloadJob::CancelRequestWithOffset(int64_t offset) {
106 if (initial_request_offset_ == offset) {
107 DownloadJobImpl::Cancel(false);
108 return;
109 }
110
111 auto it = workers_.find(offset);
112 DCHECK(it != workers_.end());
113 it->second->Cancel(false);
114 }
115
BuildParallelRequestAfterDelay()116 void ParallelDownloadJob::BuildParallelRequestAfterDelay() {
117 DCHECK(workers_.empty());
118 DCHECK(!requests_sent_);
119 DCHECK(!timer_.IsRunning());
120
121 timer_.Start(FROM_HERE, GetParallelRequestDelayConfig(), this,
122 &ParallelDownloadJob::BuildParallelRequests);
123 }
124
OnInputStreamReady(DownloadWorker * worker,std::unique_ptr<InputStream> input_stream,std::unique_ptr<DownloadCreateInfo> download_create_info)125 void ParallelDownloadJob::OnInputStreamReady(
126 DownloadWorker* worker,
127 std::unique_ptr<InputStream> input_stream,
128 std::unique_ptr<DownloadCreateInfo> download_create_info) {
129 bool success =
130 DownloadJob::AddInputStream(std::move(input_stream), worker->offset());
131
132 // Destroy the request if the sink is gone.
133 if (!success) {
134 VLOG(kDownloadJobVerboseLevel)
135 << "Byte stream arrived after download file is released.";
136 worker->Cancel(false);
137 }
138 }
139
BuildParallelRequests()140 void ParallelDownloadJob::BuildParallelRequests() {
141 DCHECK(!requests_sent_);
142 DCHECK(!is_paused());
143 if (is_canceled_ ||
144 download_item_->GetState() != DownloadItem::DownloadState::IN_PROGRESS) {
145 return;
146 }
147
148 // TODO(qinmin): The size of |slices_to_download| should be no larger than
149 // |kParallelRequestCount| unless |kParallelRequestCount| is changed after
150 // a download is interrupted. This could happen if we use finch to config
151 // the number of parallel requests.
152 // Get the next |kParallelRequestCount - 1| slices and fork
153 // new requests. For the remaining slices, they will be handled once some
154 // of the workers finish their job.
155 const DownloadItem::ReceivedSlices& received_slices =
156 download_item_->GetReceivedSlices();
157 DownloadItem::ReceivedSlices slices_to_download =
158 FindSlicesToDownload(received_slices);
159
160 DCHECK(!slices_to_download.empty());
161 int64_t first_slice_offset = slices_to_download[0].offset;
162
163 // We may build parallel job without slices. The slices can be cleared or
164 // previous session only has one stream writing to disk. In these cases, fall
165 // back to non parallel download.
166 if (initial_request_offset_ > first_slice_offset) {
167 VLOG(kDownloadJobVerboseLevel)
168 << "Received slices data mismatch initial request offset.";
169 return;
170 }
171
172 // Create more slices for a new download. The initial request may generate
173 // a received slice.
174 if (slices_to_download.size() <= 1 && download_item_->GetTotalBytes() > 0) {
175 int64_t current_bytes_per_second =
176 std::max(static_cast<int64_t>(1), download_item_->CurrentSpeed());
177 int64_t remaining_bytes =
178 download_item_->GetTotalBytes() - download_item_->GetReceivedBytes();
179
180 if (remaining_bytes / current_bytes_per_second >
181 GetMinRemainingTimeInSeconds()) {
182 // Fork more requests to accelerate, only if one slice is left to download
183 // and remaining time seems to be long enough.
184 slices_to_download = FindSlicesForRemainingContent(
185 first_slice_offset,
186 content_length_ - first_slice_offset + initial_request_offset_,
187 GetParallelRequestCount(), GetMinSliceSize());
188 }
189 }
190
191 DCHECK(!slices_to_download.empty());
192
193 // If the last received slice is finished, remove the last request which can
194 // be out of the range of the file. E.g, the file is 100 bytes, and the last
195 // request's range header will be "Range:100-".
196 if (!received_slices.empty() && received_slices.back().finished)
197 slices_to_download.pop_back();
198
199 ForkSubRequests(slices_to_download);
200 RecordParallelDownloadRequestCount(
201 static_cast<int>(slices_to_download.size()));
202 requests_sent_ = true;
203 }
204
ForkSubRequests(const DownloadItem::ReceivedSlices & slices_to_download)205 void ParallelDownloadJob::ForkSubRequests(
206 const DownloadItem::ReceivedSlices& slices_to_download) {
207 // If the initial request is working on the first hole, don't create parallel
208 // request for this hole.
209 bool skip_first_slice = true;
210 DownloadItem::ReceivedSlices initial_slices_to_download =
211 FindSlicesToDownload(initial_received_slices_);
212 if (initial_slices_to_download.size() > 1) {
213 DCHECK_EQ(initial_request_offset_, initial_slices_to_download[0].offset);
214 int64_t first_hole_max = initial_slices_to_download[0].offset +
215 initial_slices_to_download[0].received_bytes;
216 skip_first_slice = slices_to_download[0].offset <= first_hole_max;
217 }
218
219 for (auto it = slices_to_download.begin(); it != slices_to_download.end();
220 ++it) {
221 if (skip_first_slice) {
222 skip_first_slice = false;
223 continue;
224 }
225
226 DCHECK_GE(it->offset, initial_request_offset_);
227 // All parallel requests are half open, which sends request headers like
228 // "Range:50-".
229 // If server rejects a certain request, others should take over.
230 CreateRequest(it->offset);
231 }
232 }
233
CreateRequest(int64_t offset)234 void ParallelDownloadJob::CreateRequest(int64_t offset) {
235 DCHECK(download_item_);
236
237 auto worker = std::make_unique<DownloadWorker>(this, offset);
238
239 net::NetworkTrafficAnnotationTag traffic_annotation =
240 net::DefineNetworkTrafficAnnotation("parallel_download_job", R"(
241 semantics {
242 sender: "Parallel Download"
243 description:
244 "Chrome makes parallel request to speed up download of a file."
245 trigger:
246 "When user starts a download request, if it would be technically "
247 "possible, Chrome starts parallel downloading."
248 data: "None."
249 destination: WEBSITE
250 }
251 policy {
252 cookies_allowed: YES
253 cookies_store: "user"
254 setting: "This feature cannot be disabled in settings."
255 chrome_policy {
256 DownloadRestrictions {
257 DownloadRestrictions: 3
258 }
259 }
260 })");
261 // The parallel requests only use GET method.
262 std::unique_ptr<DownloadUrlParameters> download_params(
263 new DownloadUrlParameters(download_item_->GetURL(), traffic_annotation));
264 download_params->set_file_path(download_item_->GetFullPath());
265 download_params->set_last_modified(download_item_->GetLastModifiedTime());
266 download_params->set_etag(download_item_->GetETag());
267 download_params->set_offset(offset);
268
269 // Subsequent range requests don't need the "If-Range" header.
270 download_params->set_use_if_range(false);
271
272 // Subsequent range requests have the same referrer URL as the original
273 // download request.
274 download_params->set_referrer(download_item_->GetReferrerUrl());
275 download_params->set_referrer_policy(net::ReferrerPolicy::NEVER_CLEAR);
276
277 // TODO(xingliu): We should not support redirect at all for parallel requests.
278 // Currently the network service code path still can redirect as long as it's
279 // the same origin.
280 download_params->set_cross_origin_redirects(
281 network::mojom::RedirectMode::kError);
282
283 // Send the request.
284 mojo::PendingRemote<device::mojom::WakeLockProvider> wake_lock_provider;
285 wake_lock_provider_binder_.Run(
286 wake_lock_provider.InitWithNewPipeAndPassReceiver());
287 worker->SendRequest(std::move(download_params),
288 url_loader_factory_provider_.get(),
289 std::move(wake_lock_provider));
290 DCHECK(workers_.find(offset) == workers_.end());
291 workers_[offset] = std::move(worker);
292 }
293
294 } // namespace download
295