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