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