blob: 706668e6930a88928cfb72b251982b51fb9b09ba [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/proxy_resolution/polling_proxy_config_service.h"
#include <memory>
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/observer_list.h"
#include "base/synchronization/lock.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "net/proxy_resolution/proxy_config_with_annotation.h"
namespace net {
// Reference-counted wrapper that does all the work (needs to be
// reference-counted since we post tasks between threads; may outlive
// the parent PollingProxyConfigService).
class PollingProxyConfigService::Core
: public base::RefCountedThreadSafe<PollingProxyConfigService::Core> {
public:
Core(base::TimeDelta poll_interval,
GetConfigFunction get_config_func,
const NetworkTrafficAnnotationTag& traffic_annotation)
: get_config_func_(get_config_func),
poll_interval_(poll_interval),
traffic_annotation_(traffic_annotation) {}
// Called when the parent PollingProxyConfigService is destroyed
// (observers should not be called past this point).
void Orphan() {
base::AutoLock lock(lock_);
origin_task_runner_ = nullptr;
}
bool GetLatestProxyConfig(ProxyConfigWithAnnotation* config) {
LazyInitializeOriginLoop();
DCHECK(origin_task_runner_->BelongsToCurrentThread());
OnLazyPoll();
// If we have already retrieved the proxy settings (on worker thread)
// then return what we last saw.
if (has_config_) {
*config = last_config_;
return true;
}
return false;
}
void AddObserver(Observer* observer) {
LazyInitializeOriginLoop();
DCHECK(origin_task_runner_->BelongsToCurrentThread());
observers_.AddObserver(observer);
}
void RemoveObserver(Observer* observer) {
DCHECK(origin_task_runner_->BelongsToCurrentThread());
observers_.RemoveObserver(observer);
}
// Check for a new configuration if enough time has elapsed.
void OnLazyPoll() {
LazyInitializeOriginLoop();
DCHECK(origin_task_runner_->BelongsToCurrentThread());
if (last_poll_time_.is_null() ||
(base::TimeTicks::Now() - last_poll_time_) > poll_interval_) {
CheckForChangesNow();
}
}
void CheckForChangesNow() {
LazyInitializeOriginLoop();
DCHECK(origin_task_runner_->BelongsToCurrentThread());
if (poll_task_outstanding_) {
// Only allow one task to be outstanding at a time. If we get a poll
// request while we are busy, we will defer it until the current poll
// completes.
poll_task_queued_ = true;
return;
}
last_poll_time_ = base::TimeTicks::Now();
poll_task_outstanding_ = true;
poll_task_queued_ = false;
base::ThreadPool::PostTask(
FROM_HERE,
{base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&Core::PollAsync, this, get_config_func_));
}
private:
friend class base::RefCountedThreadSafe<Core>;
~Core() = default;
void PollAsync(GetConfigFunction func) {
ProxyConfigWithAnnotation config;
func(traffic_annotation_, &config);
base::AutoLock lock(lock_);
if (origin_task_runner_.get()) {
origin_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&Core::GetConfigCompleted, this, config));
}
}
// Called after the worker thread has finished retrieving a configuration.
void GetConfigCompleted(const ProxyConfigWithAnnotation& config) {
DCHECK(poll_task_outstanding_);
poll_task_outstanding_ = false;
if (!origin_task_runner_.get())
return; // Was orphaned (parent has already been destroyed).
DCHECK(origin_task_runner_->BelongsToCurrentThread());
if (!has_config_ || !last_config_.value().Equals(config.value())) {
// If the configuration has changed, notify the observers.
has_config_ = true;
last_config_ = config;
for (auto& observer : observers_)
observer.OnProxyConfigChanged(config, ProxyConfigService::CONFIG_VALID);
}
if (poll_task_queued_)
CheckForChangesNow();
}
void LazyInitializeOriginLoop() {
// TODO(eroman): Really this should be done in the constructor, but some
// consumers constructing the ProxyConfigService on threads
// other than the ProxyConfigService's main thread, so we
// can't cache the main thread for the purpose of DCHECKs
// until the first call is made.
if (!have_initialized_origin_runner_) {
origin_task_runner_ = base::SingleThreadTaskRunner::GetCurrentDefault();
have_initialized_origin_runner_ = true;
}
}
GetConfigFunction get_config_func_;
base::ObserverList<Observer>::Unchecked observers_;
ProxyConfigWithAnnotation last_config_;
base::TimeTicks last_poll_time_;
base::TimeDelta poll_interval_;
const NetworkTrafficAnnotationTag traffic_annotation_;
base::Lock lock_;
scoped_refptr<base::SingleThreadTaskRunner> origin_task_runner_;
bool have_initialized_origin_runner_ = false;
bool has_config_ = false;
bool poll_task_outstanding_ = false;
bool poll_task_queued_ = false;
};
void PollingProxyConfigService::AddObserver(Observer* observer) {
core_->AddObserver(observer);
}
void PollingProxyConfigService::RemoveObserver(Observer* observer) {
core_->RemoveObserver(observer);
}
ProxyConfigService::ConfigAvailability
PollingProxyConfigService::GetLatestProxyConfig(
ProxyConfigWithAnnotation* config) {
return core_->GetLatestProxyConfig(config) ? CONFIG_VALID : CONFIG_PENDING;
}
void PollingProxyConfigService::OnLazyPoll() {
core_->OnLazyPoll();
}
bool PollingProxyConfigService::UsesPolling() {
return true;
}
PollingProxyConfigService::PollingProxyConfigService(
base::TimeDelta poll_interval,
GetConfigFunction get_config_func,
const NetworkTrafficAnnotationTag& traffic_annotation)
: core_(base::MakeRefCounted<Core>(poll_interval,
get_config_func,
traffic_annotation)) {}
PollingProxyConfigService::~PollingProxyConfigService() {
core_->Orphan();
}
void PollingProxyConfigService::CheckForChangesNow() {
core_->CheckForChangesNow();
}
} // namespace net