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