OLD | NEW |
| (Empty) |
1 // Copyright 2016 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/offline_pages/background/pick_request_task.h" | |
6 | |
7 #include "base/bind.h" | |
8 #include "base/logging.h" | |
9 #include "base/time/time.h" | |
10 #include "components/offline_pages/background/device_conditions.h" | |
11 #include "components/offline_pages/background/offliner_policy.h" | |
12 #include "components/offline_pages/background/request_coordinator_event_logger.h
" | |
13 #include "components/offline_pages/background/request_notifier.h" | |
14 #include "components/offline_pages/background/request_queue_store.h" | |
15 #include "components/offline_pages/background/save_page_request.h" | |
16 | |
17 namespace { | |
18 template <typename T> | |
19 int signum(T t) { | |
20 return (T(0) < t) - (t < T(0)); | |
21 } | |
22 | |
23 #define CALL_MEMBER_FUNCTION(object, ptrToMember) ((object)->*(ptrToMember)) | |
24 } // namespace | |
25 | |
26 namespace offline_pages { | |
27 | |
28 PickRequestTask::PickRequestTask(RequestQueueStore* store, | |
29 OfflinerPolicy* policy, | |
30 RequestNotifier* notifier, | |
31 RequestCoordinatorEventLogger* event_logger, | |
32 RequestPickedCallback picked_callback, | |
33 RequestNotPickedCallback not_picked_callback, | |
34 RequestCountCallback request_count_callback, | |
35 DeviceConditions& device_conditions, | |
36 const std::set<int64_t>& disabled_requests) | |
37 : store_(store), | |
38 policy_(policy), | |
39 notifier_(notifier), | |
40 event_logger_(event_logger), | |
41 picked_callback_(picked_callback), | |
42 not_picked_callback_(not_picked_callback), | |
43 request_count_callback_(request_count_callback), | |
44 disabled_requests_(disabled_requests), | |
45 weak_ptr_factory_(this) { | |
46 device_conditions_.reset(new DeviceConditions(device_conditions)); | |
47 } | |
48 | |
49 PickRequestTask::~PickRequestTask() {} | |
50 | |
51 void PickRequestTask::Run() { | |
52 // Get all the requests from the queue, we will classify them in the callback. | |
53 store_->GetRequests(base::Bind(&PickRequestTask::ChooseAndPrune, | |
54 weak_ptr_factory_.GetWeakPtr())); | |
55 } | |
56 | |
57 void PickRequestTask::ChooseAndPrune( | |
58 bool success, | |
59 std::vector<std::unique_ptr<SavePageRequest>> requests) { | |
60 // If there is nothing to do, return right away. | |
61 if (requests.empty()) { | |
62 request_count_callback_.Run(requests.size(), 0); | |
63 not_picked_callback_.Run(false); | |
64 TaskComplete(); | |
65 return; | |
66 } | |
67 | |
68 // Get the expired requests to be removed from the queue, and the valid ones | |
69 // from which to pick the next request. | |
70 std::vector<std::unique_ptr<SavePageRequest>> valid_requests; | |
71 std::vector<int64_t> expired_request_ids; | |
72 SplitRequests(std::move(requests), &valid_requests, &expired_request_ids); | |
73 | |
74 // Continue processing by choosing a request. | |
75 ChooseRequestAndCallback(std::move(valid_requests)); | |
76 | |
77 // Continue processing by handling expired requests, if any. | |
78 if (expired_request_ids.size() == 0) { | |
79 TaskComplete(); | |
80 return; | |
81 } | |
82 | |
83 RemoveStaleRequests(std::move(expired_request_ids)); | |
84 } | |
85 | |
86 void PickRequestTask::ChooseRequestAndCallback( | |
87 std::vector<std::unique_ptr<SavePageRequest>> valid_requests) { | |
88 // Pick the most deserving request for our conditions. | |
89 const SavePageRequest* picked_request = nullptr; | |
90 | |
91 RequestCompareFunction comparator = nullptr; | |
92 | |
93 // Choose which comparison function to use based on policy. | |
94 if (policy_->RetryCountIsMoreImportantThanRecency()) | |
95 comparator = &PickRequestTask::RetryCountFirstCompareFunction; | |
96 else | |
97 comparator = &PickRequestTask::RecencyFirstCompareFunction; | |
98 | |
99 // TODO(petewil): Consider replacing this bool with a better named enum. | |
100 bool non_user_requested_tasks_remaining = false; | |
101 | |
102 size_t available_request_count = 0; | |
103 | |
104 // Iterate once through the requests, keeping track of best candidate. | |
105 for (unsigned i = 0; i < valid_requests.size(); ++i) { | |
106 // If the request is on the disabled list, skip it. | |
107 auto search = disabled_requests_.find(valid_requests[i]->request_id()); | |
108 if (search != disabled_requests_.end()) | |
109 continue; | |
110 // If there are non-user-requested tasks remaining, we need to make sure | |
111 // that they are scheduled after we run out of user requested tasks. Here we | |
112 // detect if any exist. If we don't find any user-requested tasks, we will | |
113 // inform the "not_picked_callback_" that it needs to schedule a task for | |
114 // non-user-requested items, which have different network and power needs. | |
115 if (!valid_requests[i]->user_requested()) | |
116 non_user_requested_tasks_remaining = true; | |
117 if (valid_requests[i]->request_state() == | |
118 SavePageRequest::RequestState::AVAILABLE) { | |
119 available_request_count++; | |
120 } | |
121 if (!RequestConditionsSatisfied(valid_requests[i].get())) | |
122 continue; | |
123 if (IsNewRequestBetter(picked_request, valid_requests[i].get(), comparator)) | |
124 picked_request = valid_requests[i].get(); | |
125 } | |
126 | |
127 // Report the request queue counts. | |
128 request_count_callback_.Run(valid_requests.size(), available_request_count); | |
129 | |
130 // If we have a best request to try next, get the request coodinator to | |
131 // start it. Otherwise return that we have no candidates. | |
132 if (picked_request != nullptr) | |
133 picked_callback_.Run(*picked_request); | |
134 else | |
135 not_picked_callback_.Run(non_user_requested_tasks_remaining); | |
136 } | |
137 | |
138 // Continue the async part of the processing by deleting the expired requests. | |
139 // TODO(petewil): Does that really need to be done on the task queue? Hard to | |
140 // see how we need to wait for it before starting the next task. OTOH, we'd hate | |
141 // to do a second slow DB operation to get entries a second time, and waiting | |
142 // until this is done will make sure other gets don't see these old entries. | |
143 // Consider moving this to a fresh task type to clean the queue. | |
144 void PickRequestTask::RemoveStaleRequests( | |
145 std::vector<int64_t> stale_request_ids) { | |
146 store_->RemoveRequests(stale_request_ids, | |
147 base::Bind(&PickRequestTask::OnRequestsExpired, | |
148 weak_ptr_factory_.GetWeakPtr())); | |
149 } | |
150 | |
151 void PickRequestTask::OnRequestsExpired( | |
152 std::unique_ptr<UpdateRequestsResult> result) { | |
153 RequestNotifier::BackgroundSavePageResult save_page_result( | |
154 RequestNotifier::BackgroundSavePageResult::EXPIRED); | |
155 for (const auto& request : result->updated_items) { | |
156 event_logger_->RecordDroppedSavePageRequest( | |
157 request.client_id().name_space, save_page_result, request.request_id()); | |
158 notifier_->NotifyCompleted(request, save_page_result); | |
159 } | |
160 | |
161 // The task is now done, return control to the task queue. | |
162 TaskComplete(); | |
163 } | |
164 | |
165 void PickRequestTask::SplitRequests( | |
166 std::vector<std::unique_ptr<SavePageRequest>> requests, | |
167 std::vector<std::unique_ptr<SavePageRequest>>* valid_requests, | |
168 std::vector<int64_t>* expired_request_ids) { | |
169 for (auto& request : requests) { | |
170 if (base::Time::Now() - request->creation_time() >= | |
171 base::TimeDelta::FromSeconds(kRequestExpirationTimeInSeconds)) { | |
172 expired_request_ids->push_back(request->request_id()); | |
173 } else { | |
174 valid_requests->push_back(std::move(request)); | |
175 } | |
176 } | |
177 } | |
178 | |
179 // Filter out requests that don't meet the current conditions. For instance, if | |
180 // this is a predictive request, and we are not on WiFi, it should be ignored | |
181 // this round. | |
182 bool PickRequestTask::RequestConditionsSatisfied( | |
183 const SavePageRequest* request) { | |
184 // If the user did not request the page directly, make sure we are connected | |
185 // to power and have WiFi and sufficient battery remaining before we take this | |
186 // request. | |
187 if (!device_conditions_->IsPowerConnected() && | |
188 policy_->PowerRequired(request->user_requested())) { | |
189 return false; | |
190 } | |
191 | |
192 if (device_conditions_->GetNetConnectionType() != | |
193 net::NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI && | |
194 policy_->UnmeteredNetworkRequired(request->user_requested())) { | |
195 return false; | |
196 } | |
197 | |
198 if (device_conditions_->GetBatteryPercentage() < | |
199 policy_->BatteryPercentageRequired(request->user_requested())) { | |
200 return false; | |
201 } | |
202 | |
203 // If we have already started this page the max number of times, it is not | |
204 // eligible to try again. | |
205 if (request->started_attempt_count() >= policy_->GetMaxStartedTries()) | |
206 return false; | |
207 | |
208 // If we have already completed trying this page the max number of times, it | |
209 // is not eligible to try again. | |
210 if (request->completed_attempt_count() >= policy_->GetMaxCompletedTries()) | |
211 return false; | |
212 | |
213 // If the request is paused, do not consider it. | |
214 if (request->request_state() == SavePageRequest::RequestState::PAUSED) | |
215 return false; | |
216 | |
217 // If the request is expired, do not consider it. | |
218 base::TimeDelta requestAge = base::Time::Now() - request->creation_time(); | |
219 if (requestAge > base::TimeDelta::FromSeconds( | |
220 policy_->GetRequestExpirationTimeInSeconds())) | |
221 return false; | |
222 | |
223 // If this request is not active yet, return false. | |
224 // TODO(petewil): If the only reason we return nothing to do is that we have | |
225 // inactive requests, we still want to try again later after their activation | |
226 // time elapses, we shouldn't take ourselves completely off the scheduler. | |
227 if (request->activation_time() > base::Time::Now()) | |
228 return false; | |
229 | |
230 return true; | |
231 } | |
232 | |
233 // Look at policies to decide which requests to prefer. | |
234 bool PickRequestTask::IsNewRequestBetter(const SavePageRequest* oldRequest, | |
235 const SavePageRequest* newRequest, | |
236 RequestCompareFunction comparator) { | |
237 // If there is no old request, the new one is better. | |
238 if (oldRequest == nullptr) | |
239 return true; | |
240 | |
241 // User requested pages get priority. | |
242 if (newRequest->user_requested() && !oldRequest->user_requested()) | |
243 return true; | |
244 | |
245 // Otherwise, use the comparison function for the current policy, which | |
246 // returns true if the older request is better. | |
247 return !(CALL_MEMBER_FUNCTION(this, comparator)(oldRequest, newRequest)); | |
248 } | |
249 | |
250 // Compare the results, checking request count before recency. Returns true if | |
251 // left hand side is better, false otherwise. | |
252 bool PickRequestTask::RetryCountFirstCompareFunction( | |
253 const SavePageRequest* left, | |
254 const SavePageRequest* right) { | |
255 // Check the attempt count. | |
256 int result = CompareRetryCount(left, right); | |
257 | |
258 if (result != 0) | |
259 return (result > 0); | |
260 | |
261 // If we get here, the attempt counts were the same, so check recency. | |
262 result = CompareCreationTime(left, right); | |
263 | |
264 return (result > 0); | |
265 } | |
266 | |
267 // Compare the results, checking recency before request count. Returns true if | |
268 // left hand side is better, false otherwise. | |
269 bool PickRequestTask::RecencyFirstCompareFunction( | |
270 const SavePageRequest* left, | |
271 const SavePageRequest* right) { | |
272 // Check the recency. | |
273 int result = CompareCreationTime(left, right); | |
274 | |
275 if (result != 0) | |
276 return (result > 0); | |
277 | |
278 // If we get here, the recency was the same, so check the attempt count. | |
279 result = CompareRetryCount(left, right); | |
280 | |
281 return (result > 0); | |
282 } | |
283 | |
284 // Compare left and right side, returning 1 if the left side is better | |
285 // (preferred by policy), 0 if the same, and -1 if the right side is better. | |
286 int PickRequestTask::CompareRetryCount(const SavePageRequest* left, | |
287 const SavePageRequest* right) { | |
288 // Check the attempt count. | |
289 int result = signum(left->completed_attempt_count() - | |
290 right->completed_attempt_count()); | |
291 | |
292 // Flip the direction of comparison if policy prefers fewer retries. | |
293 if (policy_->ShouldPreferUntriedRequests()) | |
294 result *= -1; | |
295 | |
296 return result; | |
297 } | |
298 | |
299 // Compare left and right side, returning 1 if the left side is better | |
300 // (preferred by policy), 0 if the same, and -1 if the right side is better. | |
301 int PickRequestTask::CompareCreationTime(const SavePageRequest* left, | |
302 const SavePageRequest* right) { | |
303 // Check the recency. | |
304 base::TimeDelta difference = left->creation_time() - right->creation_time(); | |
305 int result = signum(difference.InMilliseconds()); | |
306 | |
307 // Flip the direction of comparison if policy prefers fewer retries. | |
308 if (policy_->ShouldPreferEarlierRequests()) | |
309 result *= -1; | |
310 | |
311 return result; | |
312 } | |
313 | |
314 } // namespace offline_pages | |
OLD | NEW |