1 // Copyright 2012 The Chromium Authors
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 "net/proxy_resolution/polling_proxy_config_service.h"
6
7 #include <memory>
8
9 #include "base/functional/bind.h"
10 #include "base/location.h"
11 #include "base/observer_list.h"
12 #include "base/synchronization/lock.h"
13 #include "base/task/single_thread_task_runner.h"
14 #include "base/task/thread_pool.h"
15 #include "net/proxy_resolution/proxy_config_with_annotation.h"
16
17 namespace net {
18
19 // Reference-counted wrapper that does all the work (needs to be
20 // reference-counted since we post tasks between threads; may outlive
21 // the parent PollingProxyConfigService).
22 class PollingProxyConfigService::Core
23 : public base::RefCountedThreadSafe<PollingProxyConfigService::Core> {
24 public:
Core(base::TimeDelta poll_interval,GetConfigFunction get_config_func,const NetworkTrafficAnnotationTag & traffic_annotation)25 Core(base::TimeDelta poll_interval,
26 GetConfigFunction get_config_func,
27 const NetworkTrafficAnnotationTag& traffic_annotation)
28 : get_config_func_(get_config_func),
29 poll_interval_(poll_interval),
30 traffic_annotation_(traffic_annotation) {}
31
32 // Called when the parent PollingProxyConfigService is destroyed
33 // (observers should not be called past this point).
Orphan()34 void Orphan() {
35 base::AutoLock lock(lock_);
36 origin_task_runner_ = nullptr;
37 }
38
GetLatestProxyConfig(ProxyConfigWithAnnotation * config)39 bool GetLatestProxyConfig(ProxyConfigWithAnnotation* config) {
40 LazyInitializeOriginLoop();
41 DCHECK(origin_task_runner_->BelongsToCurrentThread());
42
43 OnLazyPoll();
44
45 // If we have already retrieved the proxy settings (on worker thread)
46 // then return what we last saw.
47 if (has_config_) {
48 *config = last_config_;
49 return true;
50 }
51 return false;
52 }
53
AddObserver(Observer * observer)54 void AddObserver(Observer* observer) {
55 LazyInitializeOriginLoop();
56 DCHECK(origin_task_runner_->BelongsToCurrentThread());
57 observers_.AddObserver(observer);
58 }
59
RemoveObserver(Observer * observer)60 void RemoveObserver(Observer* observer) {
61 DCHECK(origin_task_runner_->BelongsToCurrentThread());
62 observers_.RemoveObserver(observer);
63 }
64
65 // Check for a new configuration if enough time has elapsed.
OnLazyPoll()66 void OnLazyPoll() {
67 LazyInitializeOriginLoop();
68 DCHECK(origin_task_runner_->BelongsToCurrentThread());
69
70 if (last_poll_time_.is_null() ||
71 (base::TimeTicks::Now() - last_poll_time_) > poll_interval_) {
72 CheckForChangesNow();
73 }
74 }
75
CheckForChangesNow()76 void CheckForChangesNow() {
77 LazyInitializeOriginLoop();
78 DCHECK(origin_task_runner_->BelongsToCurrentThread());
79
80 if (poll_task_outstanding_) {
81 // Only allow one task to be outstanding at a time. If we get a poll
82 // request while we are busy, we will defer it until the current poll
83 // completes.
84 poll_task_queued_ = true;
85 return;
86 }
87
88 last_poll_time_ = base::TimeTicks::Now();
89 poll_task_outstanding_ = true;
90 poll_task_queued_ = false;
91 base::ThreadPool::PostTask(
92 FROM_HERE,
93 {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
94 base::BindOnce(&Core::PollAsync, this, get_config_func_));
95 }
96
97 private:
98 friend class base::RefCountedThreadSafe<Core>;
99 ~Core() = default;
100
PollAsync(GetConfigFunction func)101 void PollAsync(GetConfigFunction func) {
102 ProxyConfigWithAnnotation config;
103 func(traffic_annotation_, &config);
104
105 base::AutoLock lock(lock_);
106 if (origin_task_runner_.get()) {
107 origin_task_runner_->PostTask(
108 FROM_HERE, base::BindOnce(&Core::GetConfigCompleted, this, config));
109 }
110 }
111
112 // Called after the worker thread has finished retrieving a configuration.
GetConfigCompleted(const ProxyConfigWithAnnotation & config)113 void GetConfigCompleted(const ProxyConfigWithAnnotation& config) {
114 DCHECK(poll_task_outstanding_);
115 poll_task_outstanding_ = false;
116
117 if (!origin_task_runner_.get())
118 return; // Was orphaned (parent has already been destroyed).
119
120 DCHECK(origin_task_runner_->BelongsToCurrentThread());
121
122 if (!has_config_ || !last_config_.value().Equals(config.value())) {
123 // If the configuration has changed, notify the observers.
124 has_config_ = true;
125 last_config_ = config;
126 for (auto& observer : observers_)
127 observer.OnProxyConfigChanged(config, ProxyConfigService::CONFIG_VALID);
128 }
129
130 if (poll_task_queued_)
131 CheckForChangesNow();
132 }
133
LazyInitializeOriginLoop()134 void LazyInitializeOriginLoop() {
135 // TODO(eroman): Really this should be done in the constructor, but some
136 // consumers constructing the ProxyConfigService on threads
137 // other than the ProxyConfigService's main thread, so we
138 // can't cache the main thread for the purpose of DCHECKs
139 // until the first call is made.
140 if (!have_initialized_origin_runner_) {
141 origin_task_runner_ = base::SingleThreadTaskRunner::GetCurrentDefault();
142 have_initialized_origin_runner_ = true;
143 }
144 }
145
146 GetConfigFunction get_config_func_;
147 base::ObserverList<Observer>::Unchecked observers_;
148 ProxyConfigWithAnnotation last_config_;
149 base::TimeTicks last_poll_time_;
150 base::TimeDelta poll_interval_;
151
152 const NetworkTrafficAnnotationTag traffic_annotation_;
153
154 base::Lock lock_;
155 scoped_refptr<base::SingleThreadTaskRunner> origin_task_runner_;
156
157 bool have_initialized_origin_runner_ = false;
158 bool has_config_ = false;
159 bool poll_task_outstanding_ = false;
160 bool poll_task_queued_ = false;
161 };
162
AddObserver(Observer * observer)163 void PollingProxyConfigService::AddObserver(Observer* observer) {
164 core_->AddObserver(observer);
165 }
166
RemoveObserver(Observer * observer)167 void PollingProxyConfigService::RemoveObserver(Observer* observer) {
168 core_->RemoveObserver(observer);
169 }
170
171 ProxyConfigService::ConfigAvailability
GetLatestProxyConfig(ProxyConfigWithAnnotation * config)172 PollingProxyConfigService::GetLatestProxyConfig(
173 ProxyConfigWithAnnotation* config) {
174 return core_->GetLatestProxyConfig(config) ? CONFIG_VALID : CONFIG_PENDING;
175 }
176
OnLazyPoll()177 void PollingProxyConfigService::OnLazyPoll() {
178 core_->OnLazyPoll();
179 }
180
UsesPolling()181 bool PollingProxyConfigService::UsesPolling() {
182 return true;
183 }
184
PollingProxyConfigService(base::TimeDelta poll_interval,GetConfigFunction get_config_func,const NetworkTrafficAnnotationTag & traffic_annotation)185 PollingProxyConfigService::PollingProxyConfigService(
186 base::TimeDelta poll_interval,
187 GetConfigFunction get_config_func,
188 const NetworkTrafficAnnotationTag& traffic_annotation)
189 : core_(base::MakeRefCounted<Core>(poll_interval,
190 get_config_func,
191 traffic_annotation)) {}
192
~PollingProxyConfigService()193 PollingProxyConfigService::~PollingProxyConfigService() {
194 core_->Orphan();
195 }
196
CheckForChangesNow()197 void PollingProxyConfigService::CheckForChangesNow() {
198 core_->CheckForChangesNow();
199 }
200
201 } // namespace net
202