Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(107)

Side by Side Diff: chromeos/timezone/timezone_resolver.cc

Issue 834073002: ChromeOS: Implement periodic timezone refresh on geolocation data. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Switch feature to disabled by default. Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2014 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 "chromeos/timezone/timezone_resolver.h"
6
7 #include <math.h>
8
9 #include <algorithm>
10
11 #include "base/bind.h"
12 #include "base/callback.h"
13 #include "base/callback_helpers.h"
14 #include "base/logging.h"
15 #include "base/power_monitor/power_monitor.h"
16 #include "base/power_monitor/power_observer.h"
17 #include "base/prefs/pref_registry_simple.h"
18 #include "base/prefs/pref_service.h"
19 #include "base/rand_util.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "base/time/time.h"
22 #include "chromeos/geolocation/geoposition.h"
23 #include "chromeos/geolocation/simple_geolocation_provider.h"
24 #include "chromeos/timezone/timezone_provider.h"
25 #include "net/url_request/url_request_context_getter.h"
26 #include "url/gurl.h"
27
28 namespace chromeos {
29
30 namespace {
31
32 class TZRequest;
33
34 // Total timezone resolving process timeout.
35 const unsigned int kRefreshTimeZoneTimeoutSeconds = 60;
36
37 // Initial delay (for the first request).
38 const double kInitialRefreshIntervalSec = 3.0;
39
40 // Timezone refresh happens at least once each this interval.
41 const double kMaximumRefreshIntervalSec = 6.0 * 3600; // 6 hours
42
43 // Delay between refresh attempts depends on current number of requests and
44 // this constant.
45 // [interval = kInitialRefreshIntervalMS * (2 ^
46 // (kRefreshIntervalRequestsCountMultiplier * requests_count))]
47 // in seconds.
48 // request_number interval (seconds)
49 // 1 3 (initial, requests_count = 0)
50 // 2 24 (requests_count = 1)
51 // 3 1536 (requests_count = 2)
52 // 4 12288 (requests_count = 3)
53 // 5+ 21600 (maximum)
54 const unsigned int kRefreshIntervalRequestsCountMultiplier = 3;
55
56 // We should limit request rate on browser start to prevent server overload
57 // on permanent browser crash.
58 // If too little time has passed since previous request, initialize
59 // |requests_count_| with |kRefreshTimeZoneInitialRequestCountOnRateLimit|.
60 const double kRefreshTimeZoneMinimumDelayOnRestartSec =
61 10 * 60.0; // 10 minutes
62 const unsigned int kRefreshTimeZoneInitialRequestCountOnRateLimit = 2;
63
64 int MaxRequestsCountForInterval(const double interval_seconds) {
65 return log2(interval_seconds / kInitialRefreshIntervalSec) /
66 kRefreshIntervalRequestsCountMultiplier;
67 }
68
69 int IntervalForNextRequest(const int requests) {
70 const base::TimeDelta initial_interval =
71 base::TimeDelta::FromSecondsD(kInitialRefreshIntervalSec);
72 return static_cast<int>(initial_interval.InSecondsF() *
73 (2 << (static_cast<unsigned>(requests) *
74 kRefreshIntervalRequestsCountMultiplier)));
75 }
76
77 } // anonymous namespace
78
79 const char TimeZoneResolver::kLastTimeZoneRefreshTime[] =
80 "timezone_resolver.last_update_time";
81
82 // This class periodically refreshes location and timezone.
83 // It should be destroyed to stop refresh.
84 class TimeZoneResolver::TimeZoneResolverImpl : public base::PowerObserver {
85 public:
86 explicit TimeZoneResolverImpl(const TimeZoneResolver* resolver);
87
88 ~TimeZoneResolverImpl() override;
89
90 // This is called once after the object is created.
91 void Start();
92
93 // PowerObserver implementation.
94 void OnResume() override;
95
96 // (Re)Starts timer.
97 void ScheduleRequest();
98
99 // Creates new TZRequest.
100 void CreateNewRequest();
101
102 // Called by TZRequest.
103 SimpleGeolocationProvider* geolocation_provider() {
104 return &geolocation_provider_;
105 }
106 TimeZoneProvider* timezone_provider() { return &timezone_provider_; }
107
108 // Update requests count and last request time.
109 void RecordAttempt();
110
111 // This is called by TZRequest. Destroys active request and starts a new one.
112 void RequestIsFinished();
113
114 void ApplyTimeZone(const TimeZoneResponseData* timezone);
115
116 TimeZoneResolver::DelayNetworkCallClosure delay_network_call() const {
117 return resolver_->delay_network_call();
118 }
119
120 base::WeakPtr<TimeZoneResolver::TimeZoneResolverImpl> AsWeakPtr();
121
122 private:
123 const TimeZoneResolver* resolver_;
124
125 // Returns delay to next timezone update request
126 base::TimeDelta CalculateNextInterval();
127
128 SimpleGeolocationProvider geolocation_provider_;
129 TimeZoneProvider timezone_provider_;
130
131 base::OneShotTimer<TimeZoneResolver::TimeZoneResolverImpl> refresh_timer_;
132
133 // Total number of request attempts.
134 int requests_count_;
135
136 // This is not NULL when update is in progress.
137 scoped_ptr<TZRequest> request_;
138
139 base::WeakPtrFactory<TimeZoneResolver::TimeZoneResolverImpl>
140 weak_ptr_factory_;
141
142 DISALLOW_COPY_AND_ASSIGN(TimeZoneResolverImpl);
143 };
144
145 namespace {
146
147 // This class implements a single timezone refresh attempt.
148 class TZRequest {
149 public:
150 explicit TZRequest(TimeZoneResolver::TimeZoneResolverImpl* resolver)
151 : resolver_(resolver), weak_ptr_factory_(this) {}
152
153 ~TZRequest();
154
155 // Starts request after specified delay.
156 void Start();
157
158 // Called from SimpleGeolocationProvider when location is resolved.
159 void OnLocationResolved(const Geoposition& position,
160 bool server_error,
161 const base::TimeDelta elapsed);
162
163 // TimeZoneRequest::TimeZoneResponseCallback implementation.
164 void OnTimezoneResolved(scoped_ptr<TimeZoneResponseData> timezone,
165 bool server_error);
166
167 base::WeakPtr<TZRequest> AsWeakPtr();
168
169 private:
170 // This is called by network detector when network is available.
171 void StartRequestOnNetworkAvailable();
172
173 TimeZoneResolver::TimeZoneResolverImpl* const resolver_;
174
175 base::WeakPtrFactory<TZRequest> weak_ptr_factory_;
176
177 DISALLOW_COPY_AND_ASSIGN(TZRequest);
178 };
179
180 TZRequest::~TZRequest() {
181 }
182
183 void TZRequest::StartRequestOnNetworkAvailable() {
184 resolver_->RecordAttempt();
185 resolver_->geolocation_provider()->RequestGeolocation(
186 base::TimeDelta::FromSeconds(kRefreshTimeZoneTimeoutSeconds),
187 base::Bind(&TZRequest::OnLocationResolved, AsWeakPtr()));
188 }
189
190 void TZRequest::Start() {
191 // call to chromeos::DelayNetworkCall
192 resolver_->delay_network_call().Run(
193 base::Bind(&TZRequest::StartRequestOnNetworkAvailable, AsWeakPtr()));
194 }
195
196 void TZRequest::OnLocationResolved(const Geoposition& position,
197 bool server_error,
198 const base::TimeDelta elapsed) {
199 base::ScopedClosureRunner on_request_finished(
200 base::Bind(&TimeZoneResolver::TimeZoneResolverImpl::RequestIsFinished,
201 base::Unretained(resolver_)));
202
203 // Ignore invalid position.
204 if (!position.Valid())
205 return;
206
207 const base::TimeDelta timeout =
208 base::TimeDelta::FromSeconds(kRefreshTimeZoneTimeoutSeconds);
209
210 if (elapsed >= timeout) {
211 VLOG(1) << "Refresh TimeZone: got location after timeout ("
212 << elapsed.InSecondsF() << " seconds elapsed). Ignored.";
213 return;
214 }
215
216 resolver_->timezone_provider()->RequestTimezone(
217 position,
218 false, // sensor
219 timeout - elapsed,
220 base::Bind(&TZRequest::OnTimezoneResolved, AsWeakPtr()));
221
222 // Prevent |on_request_finished| from firing here.
223 base::Closure unused = on_request_finished.Release();
224 }
225
226 void TZRequest::OnTimezoneResolved(scoped_ptr<TimeZoneResponseData> timezone,
227 bool server_error) {
228 base::ScopedClosureRunner on_request_finished(
229 base::Bind(&TimeZoneResolver::TimeZoneResolverImpl::RequestIsFinished,
230 base::Unretained(resolver_)));
231
232 DCHECK(timezone);
233 VLOG(1) << "Refreshed local timezone={" << timezone->ToStringForDebug()
234 << "}.";
235
236 if (timezone->status != TimeZoneResponseData::OK) {
237 VLOG(1) << "Refresh TimeZone: failed to resolve timezone.";
238 return;
239 }
240
241 resolver_->ApplyTimeZone(timezone.get());
242 }
243
244 base::WeakPtr<TZRequest> TZRequest::AsWeakPtr() {
245 return weak_ptr_factory_.GetWeakPtr();
246 }
247
248 } // anonymous namespace
249
250 // ------------------------------------------------------------------------
251 // TimeZoneResolver::TimeZoneResolverImpl implementation.
252
253 TimeZoneResolver::TimeZoneResolverImpl::TimeZoneResolverImpl(
254 const TimeZoneResolver* resolver)
255 : resolver_(resolver),
256 geolocation_provider_(
257 resolver->context().get(),
258 SimpleGeolocationProvider::DefaultGeolocationProviderURL()),
259 timezone_provider_(resolver->context().get(),
260 DefaultTimezoneProviderURL()),
261 requests_count_(0),
262 weak_ptr_factory_(this) {
263 DCHECK(!resolver_->apply_timezone().is_null());
264 DCHECK(!resolver_->delay_network_call().is_null());
265
266 base::PowerMonitor* power_monitor = base::PowerMonitor::Get();
267 power_monitor->AddObserver(this);
268
269 const int64 last_refresh_at_raw =
270 resolver_->local_state()->GetInt64(kLastTimeZoneRefreshTime);
271 const base::Time last_refresh_at =
272 base::Time::FromInternalValue(last_refresh_at_raw);
273 const base::Time next_refresh_not_before =
274 last_refresh_at +
275 base::TimeDelta::FromSecondsD(kRefreshTimeZoneMinimumDelayOnRestartSec);
276 if (next_refresh_not_before > base::Time::Now()) {
277 requests_count_ = kRefreshTimeZoneInitialRequestCountOnRateLimit;
278 VLOG(1) << "TimeZoneResolverImpl(): initialize requests_count_="
279 << requests_count_ << " because of rate limit.";
280 }
281 }
282
283 TimeZoneResolver::TimeZoneResolverImpl::~TimeZoneResolverImpl() {
284 base::PowerMonitor* power_monitor = base::PowerMonitor::Get();
285 if (power_monitor)
286 power_monitor->RemoveObserver(this);
287 }
288
289 void TimeZoneResolver::TimeZoneResolverImpl::Start() {
290 // Start() is usually called twice:
291 // - On device boot.
292 // - On user session start.
293 if (request_ || refresh_timer_.IsRunning())
294 return;
295
296 ScheduleRequest();
297 }
298
299 // Returns delay to next timezone update request
300 base::TimeDelta
301 TimeZoneResolver::TimeZoneResolverImpl::CalculateNextInterval() {
302 // This is initial request, which should be served immediately.
303 if (requests_count_ == 0) {
304 return base::TimeDelta::FromSecondsD(kInitialRefreshIntervalSec);
305 }
306
307 // See comment to kRefreshIntervalRequestsCountMultiplier.
308 if (requests_count_ >=
309 MaxRequestsCountForInterval(kMaximumRefreshIntervalSec)) {
310 return base::TimeDelta::FromSecondsD(kMaximumRefreshIntervalSec);
311 }
312
313 const int base_interval = IntervalForNextRequest(requests_count_);
314 DCHECK_LE(base_interval, kMaximumRefreshIntervalSec);
315
316 // Add jitter to level request rate.
317 const base::TimeDelta interval(
318 base::TimeDelta::FromSecondsD(base::RandDouble() * 2 * base_interval));
319 VLOG(1) << "TimeZoneResolverImpl::CalculateNextInterval(): interval="
320 << interval.InSecondsF();
321 return interval;
322 }
323
324 void TimeZoneResolver::TimeZoneResolverImpl::OnResume() {
325 requests_count_ = 0;
326 // Refresh timezone immediately.
327 request_.reset();
328 ScheduleRequest();
329 }
330
331 void TimeZoneResolver::TimeZoneResolverImpl::ScheduleRequest() {
332 if (request_)
333 return;
334
335 // base::OneShotTimer
336 base::TimeDelta interval = CalculateNextInterval();
337 refresh_timer_.Stop();
338 refresh_timer_.Start(
339 FROM_HERE, interval,
340 base::Bind(&TimeZoneResolver::TimeZoneResolverImpl::CreateNewRequest,
341 AsWeakPtr()));
342 }
343
344 void TimeZoneResolver::TimeZoneResolverImpl::CreateNewRequest() {
345 if (request_)
346 return;
347
348 refresh_timer_.Stop();
349
350 request_.reset(new TZRequest(this));
351 request_->Start();
352 }
353
354 void TimeZoneResolver::TimeZoneResolverImpl::RecordAttempt() {
355 resolver_->local_state()->SetInt64(kLastTimeZoneRefreshTime,
356 base::Time::Now().ToInternalValue());
357 ++requests_count_;
358 }
359
360 void TimeZoneResolver::TimeZoneResolverImpl::RequestIsFinished() {
361 request_.reset();
362 ScheduleRequest();
363 }
364
365 void TimeZoneResolver::TimeZoneResolverImpl::ApplyTimeZone(
366 const TimeZoneResponseData* timezone) {
367 resolver_->apply_timezone().Run(timezone);
368 }
369
370 base::WeakPtr<TimeZoneResolver::TimeZoneResolverImpl>
371 TimeZoneResolver::TimeZoneResolverImpl::AsWeakPtr() {
372 return weak_ptr_factory_.GetWeakPtr();
373 }
374
375 // ------------------------------------------------------------------------
376 // TimeZoneResolver implementation
377
378 TimeZoneResolver::TimeZoneResolver(
379 scoped_refptr<net::URLRequestContextGetter> context,
380 const GURL& url,
381 const ApplyTimeZoneCallback& apply_timezone,
382 const DelayNetworkCallClosure& delay_network_call,
383 PrefService* local_state)
384 : context_(context),
385 url_(url),
386 apply_timezone_(apply_timezone),
387 delay_network_call_(delay_network_call),
388 local_state_(local_state) {
389 DCHECK(!apply_timezone.is_null());
390 }
391
392 TimeZoneResolver::~TimeZoneResolver() {
393 Stop();
394 }
395
396 void TimeZoneResolver::Start() {
397 DCHECK(thread_checker_.CalledOnValidThread());
398 if (!implementation_) {
399 implementation_.reset(new TimeZoneResolverImpl(this));
400 implementation_->Start();
401 }
402 }
403
404 void TimeZoneResolver::Stop() {
405 DCHECK(thread_checker_.CalledOnValidThread());
406 implementation_.reset();
407 }
408
409 // static
410 int TimeZoneResolver::MaxRequestsCountForIntervalForTesting(
411 const double interval_seconds) {
412 return MaxRequestsCountForInterval(interval_seconds);
413 }
414
415 // static
416 int TimeZoneResolver::IntervalForNextRequestForTesting(const int requests) {
417 return IntervalForNextRequest(requests);
418 }
419
420 // static
421 void TimeZoneResolver::RegisterPrefs(PrefRegistrySimple* registry) {
422 registry->RegisterInt64Pref(kLastTimeZoneRefreshTime, 0);
423 }
424
425 } // namespace chromeos
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698