diff options
author | levin@chromium.org <levin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-09-01 06:09:30 +0000 |
---|---|---|
committer | levin@chromium.org <levin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-09-01 06:09:30 +0000 |
commit | 9490734d8a4fc608b690bdbb06c97bfdc87fff22 (patch) | |
tree | 828b05d72987bc318067517215a0eb7b3c853bf9 /net | |
parent | aae4c8d74f2367bfe9e02253cc42d3e1be07f13a (diff) | |
download | chromium_src-9490734d8a4fc608b690bdbb06c97bfdc87fff22.zip chromium_src-9490734d8a4fc608b690bdbb06c97bfdc87fff22.tar.gz chromium_src-9490734d8a4fc608b690bdbb06c97bfdc87fff22.tar.bz2 |
Revert 99135 - Refactoring and remaining work on DnsConfigService:
- Isolate WatchingFileReader for reusability and testability
- ParseHosts to parse /etc/hosts
- tests
BUG=90881
TEST=./net_unittests --gtest_filter='DnsConfig*:DnsHosts*:WatchingFileReader*'
Review URL: http://codereview.chromium.org/7753027
TBR=szym@chromium.org
Review URL: http://codereview.chromium.org/7829001
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@99138 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r-- | net/dns/dns_config_service.cc | 83 | ||||
-rw-r--r-- | net/dns/dns_config_service.h | 67 | ||||
-rw-r--r-- | net/dns/dns_config_service_posix.cc | 205 | ||||
-rw-r--r-- | net/dns/dns_config_service_posix.h | 81 | ||||
-rw-r--r-- | net/dns/dns_config_service_posix_unittest.cc | 349 | ||||
-rw-r--r-- | net/dns/dns_config_service_unittest.cc | 108 | ||||
-rw-r--r-- | net/dns/dns_hosts.cc | 47 | ||||
-rw-r--r-- | net/dns/dns_hosts.h | 41 | ||||
-rw-r--r-- | net/dns/dns_hosts_unittest.cc | 61 | ||||
-rw-r--r-- | net/dns/watching_file_reader.cc | 133 | ||||
-rw-r--r-- | net/dns/watching_file_reader.h | 133 | ||||
-rw-r--r-- | net/dns/watching_file_reader_unittest.cc | 289 | ||||
-rw-r--r-- | net/net.gyp | 7 |
13 files changed, 611 insertions, 993 deletions
diff --git a/net/dns/dns_config_service.cc b/net/dns/dns_config_service.cc index b3a27d7..f32f293 100644 --- a/net/dns/dns_config_service.cc +++ b/net/dns/dns_config_service.cc @@ -4,8 +4,6 @@ #include "net/dns/dns_config_service.h" -#include "base/file_util.h" -#include "net/base/io_buffer.h" #include "net/base/ip_endpoint.h" namespace net { @@ -20,7 +18,7 @@ DnsConfig::DnsConfig() DnsConfig::~DnsConfig() {} -bool DnsConfig::EqualsIgnoreHosts(const DnsConfig& d) const { +bool DnsConfig::Equals(const DnsConfig& d) const { return (nameservers == d.nameservers) && (search == d.search) && (ndots == d.ndots) && @@ -30,84 +28,5 @@ bool DnsConfig::EqualsIgnoreHosts(const DnsConfig& d) const { (edns0 == d.edns0); } -bool DnsConfig::Equals(const DnsConfig& d) const { - return EqualsIgnoreHosts(d) && (hosts == d.hosts); -} - -DnsConfigService::DnsConfigService() - : have_config_(false), - have_hosts_(false) {} - -DnsConfigService::~DnsConfigService() {} - -void DnsConfigService::AddObserver(Observer* observer) { - DCHECK(CalledOnValidThread()); - observers_.AddObserver(observer); - if (have_config_ && have_hosts_) { - observer->OnConfigChanged(dns_config_); - } -} - -void DnsConfigService::RemoveObserver(Observer* observer) { - DCHECK(CalledOnValidThread()); - observers_.RemoveObserver(observer); -} - -void DnsConfigService::OnConfigRead(const DnsConfig& config) { - DCHECK(CalledOnValidThread()); - if (!config.EqualsIgnoreHosts(dns_config_)) { - DnsConfig copy = config; - copy.hosts.swap(dns_config_.hosts); - dns_config_ = copy; - have_config_ = true; - if (have_hosts_) { - FOR_EACH_OBSERVER(Observer, observers_, OnConfigChanged(dns_config_)); - } - } -} - -void DnsConfigService::OnHostsRead(const DnsHosts& hosts) { - DCHECK(CalledOnValidThread()); - if (hosts != dns_config_.hosts || !have_hosts_) { - dns_config_.hosts = hosts; - have_hosts_ = true; - if (have_config_) { - FOR_EACH_OBSERVER(Observer, observers_, OnConfigChanged(dns_config_)); - } - } -} - -DnsHostsReader::DnsHostsReader(DnsConfigService* service) - : service_(service), - success_(false) { - DCHECK(service); -} - -// Reads the contents of the file at |path| into |str| if the total length is -// less than |size|. -static bool ReadFile(const FilePath& path, int64 size, std::string* str) { - int64 sz; - if (!file_util::GetFileSize(path, &sz) || sz > size) - return false; - return file_util::ReadFileToString(path, str); -} - -void DnsHostsReader::DoRead() { - success_ = false; - std::string contents; - const int64 kMaxHostsSize = 1 << 16; - if (ReadFile(get_path(), kMaxHostsSize, &contents)) { - success_ = true; - ParseHosts(contents, &dns_hosts_); - } -} - -void DnsHostsReader::OnReadFinished() { - if (success_) - service_->OnHostsRead(dns_hosts_); -} - -DnsHostsReader::~DnsHostsReader() {} - } // namespace net diff --git a/net/dns/dns_config_service.h b/net/dns/dns_config_service.h index 6ceeaa6..535dcc1 100644 --- a/net/dns/dns_config_service.h +++ b/net/dns/dns_config_service.h @@ -6,20 +6,16 @@ #define NET_DNS_DNS_CONFIG_SERVICE_H_ #pragma once -#include <map> #include <string> #include <vector> -#include "base/gtest_prod_util.h" -#include "base/observer_list.h" #include "base/time.h" -#include "net/base/ip_endpoint.h" // win requires size of IPEndPoint #include "net/base/net_export.h" -#include "net/dns/dns_hosts.h" -#include "net/dns/watching_file_reader.h" namespace net { +class IPEndPoint; + // DnsConfig stores configuration of the system resolver. struct NET_EXPORT_PRIVATE DnsConfig { DnsConfig(); @@ -27,9 +23,7 @@ struct NET_EXPORT_PRIVATE DnsConfig { bool Equals(const DnsConfig& d) const; - bool EqualsIgnoreHosts(const DnsConfig& d) const; - - bool IsValid() const { + bool Valid() const { return !nameservers.empty(); } @@ -39,12 +33,9 @@ struct NET_EXPORT_PRIVATE DnsConfig { // is less than |ndots|. std::vector<std::string> search; - DnsHosts hosts; - // Resolver options; see man resolv.conf. // TODO(szym): use |ndots| and |search| to determine the sequence of FQDNs // to query given a specific name. - // TODO(szym): implement DNS Devolution for windows // Minimum number of dots before global resolution precedes |search|. int ndots; @@ -58,11 +49,9 @@ struct NET_EXPORT_PRIVATE DnsConfig { bool edns0; }; - // Service for watching when the system DNS settings have changed. // Depending on the platform, watches files in /etc/ or win registry. -class NET_EXPORT_PRIVATE DnsConfigService - : NON_EXPORTED_BASE(public base::NonThreadSafe) { +class NET_EXPORT_PRIVATE DnsConfigService { public: // Callback interface for the client. The observer is called on the same // thread as Watch(). Observer must outlive the service. @@ -70,62 +59,28 @@ class NET_EXPORT_PRIVATE DnsConfigService public: virtual ~Observer() {} - // Called only when |dns_config| is different from the last call. + // Called only when |dns_config| is different from the last check. virtual void OnConfigChanged(const DnsConfig& dns_config) = 0; }; // Creates the platform-specific DnsConfigService. static DnsConfigService* CreateSystemService(); - DnsConfigService(); - virtual ~DnsConfigService(); + DnsConfigService() {} + virtual ~DnsConfigService() {} // Immediately starts watching system configuration for changes and attempts // to read the configuration. For some platform implementations, the current // thread must have an IO loop (for base::files::FilePathWatcher). - virtual void Watch() {} + virtual void Watch() = 0; // If a config is available, |observer| will immediately be called with // OnConfigChanged. - void AddObserver(Observer* observer); - void RemoveObserver(Observer* observer); - - protected: - FRIEND_TEST_ALL_PREFIXES(DnsConfigServiceTest, NotifyOnChange); - friend class DnsHostsReader; - // To be called with new config. |config|.hosts is ignored. - virtual void OnConfigRead(const DnsConfig& config); - // To be called with new hosts. Rest of the config is assumed unchanged. - virtual void OnHostsRead(const DnsHosts& hosts); - - DnsConfig dns_config_; - // True after first OnConfigRead and OnHostsRead; indicate complete config. - bool have_config_; - bool have_hosts_; - - ObserverList<Observer> observers_; - private: - DISALLOW_COPY_AND_ASSIGN(DnsConfigService); -}; - -// A WatchingFileReader that reads a HOSTS file and notifies -// DnsConfigService::OnHostsRead(). -class DnsHostsReader : public WatchingFileReader { - public: - explicit DnsHostsReader(DnsConfigService* service); - - virtual void DoRead() OVERRIDE; - virtual void OnReadFinished() OVERRIDE; + virtual void AddObserver(Observer* observer) = 0; + virtual void RemoveObserver(Observer* observer) = 0; private: - virtual ~DnsHostsReader(); - - DnsConfigService* service_; - // Written in DoRead, read in OnReadFinished, no locking necessary. - DnsHosts dns_hosts_; - bool success_; - - DISALLOW_COPY_AND_ASSIGN(DnsHostsReader); + DISALLOW_COPY_AND_ASSIGN(DnsConfigService); }; } // namespace net diff --git a/net/dns/dns_config_service_posix.cc b/net/dns/dns_config_service_posix.cc index c8b4ab1..c79e43c 100644 --- a/net/dns/dns_config_service_posix.cc +++ b/net/dns/dns_config_service_posix.cc @@ -6,10 +6,16 @@ #include <string> +#include "base/bind.h" #include "base/compiler_specific.h" #include "base/file_path.h" -#include "base/file_util.h" +#include "base/files/file_path_watcher.h" +#include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/message_loop_proxy.h" +#include "base/observer_list.h" +#include "base/threading/worker_pool.h" #include "net/base/ip_endpoint.h" #include "net/base/net_util.h" @@ -19,53 +25,198 @@ namespace net { -// A WatcherDelegate that uses ResolverLib to initialize res_state and converts -// it to DnsConfig. -class DnsConfigServicePosix::DnsConfigReader : public WatchingFileReader { +// FilePathWatcher::Delegate is refcounted, so we separate it from the Service +// It also hosts callbacks on the WorkerPool. +class DnsConfigServicePosix::WatcherDelegate + : public base::files::FilePathWatcher::Delegate { public: - explicit DnsConfigReader(DnsConfigServicePosix* service) + // Takes ownership of |lib|. + WatcherDelegate(DnsConfigServicePosix* service, + DnsConfigServicePosix::ResolverLib* lib) : service_(service), - success_(false) {} + resolver_lib_(lib), + message_loop_(base::MessageLoopProxy::current()), + reading_(false), + read_pending_(false) {} - void DoRead() OVERRIDE { - struct __res_state res; - if ((res_ninit(&res) == 0) && (res.options & RES_INIT)) { - success_ = ConvertResToConfig(res, &dns_config_); + void Cancel() { + DCHECK(message_loop_->BelongsToCurrentThread()); + service_ = NULL; + } + + void RescheduleWatch() { + DCHECK(message_loop_->BelongsToCurrentThread()); + // Retry Watch in 100ms or so. + message_loop_->PostDelayedTask( + FROM_HERE, base::Bind(&WatcherDelegate::StartWatch, this), 100); + } + + // FilePathWatcher::Delegate interface + virtual void OnFilePathChanged(const FilePath& path) OVERRIDE { + DCHECK(message_loop_->BelongsToCurrentThread()); + if (!service_) + return; + ScheduleRead(); + } + + virtual void OnFilePathError(const FilePath& path) OVERRIDE { + DCHECK(message_loop_->BelongsToCurrentThread()); + StartWatch(); + } + + private: + virtual ~WatcherDelegate() {} + + // Unless already scheduled, post DoRead to WorkerPool. + void ScheduleRead() { + if (reading_) { + // Mark that we need to re-read after DoRead posts results. + read_pending_ = true; } else { - // Note: res_ninit in glibc always returns 0 and sets RES_INIT. - success_ = false; + if (!base::WorkerPool::PostTask(FROM_HERE, base::Bind( + &WatcherDelegate::DoRead, this), false)) { + // See worker_pool_posix.cc. + NOTREACHED() << "WorkerPool::PostTask is not expected to fail on posix"; + } + reading_ = true; + read_pending_ = false; + } + } + + // Reads DnsConfig and posts OnResultAvailable to |message_loop_|. + // Must be called on the worker thread. + void DoRead() { + DnsConfig config; + struct __res_state res; + bool success = false; + if (resolver_lib_->ninit(&res) == 0) { + success = ConvertResToConfig(res, &config); + resolver_lib_->nclose(&res); } - res_nclose(&res); + // If this fails, the loop is gone, so there is no point retrying. + message_loop_->PostTask(FROM_HERE, base::Bind( + &WatcherDelegate::OnResultAvailable, this, config, success)); } - void OnReadFinished() OVERRIDE { - if (success_) - service_->OnConfigRead(dns_config_); + // Communicates result to the service. Must be called on the the same thread + // that constructed WatcherDelegate. + void OnResultAvailable(const DnsConfig &config, bool success) { + DCHECK(message_loop_->BelongsToCurrentThread()); + if (!service_) + return; + reading_ = false; + if (read_pending_) { + // Discard this result and re-schedule. + ScheduleRead(); + return; + } + if (!success) { + VLOG(1) << "Failed to read DnsConfig"; + } else { + service_->OnConfigRead(config); + } } - private: - virtual ~DnsConfigReader() {} + // To avoid refcounting the service, use this method in tasks/callbacks. + void StartWatch() { + if (!service_) + return; + service_->StartWatch(); + } DnsConfigServicePosix* service_; - // Written in DoRead, read in OnReadFinished, no locking necessary. - DnsConfig dns_config_; - bool success_; + scoped_ptr<DnsConfigServicePosix::ResolverLib> resolver_lib_; + // Message loop for the thread on which Watch is called (of TYPE_IO). + scoped_refptr<base::MessageLoopProxy> message_loop_; + // True after DoRead before OnResultsAvailable. + bool reading_; + // True after OnFilePathChanged fires while |reading_| is true. + bool read_pending_; }; DnsConfigServicePosix::DnsConfigServicePosix() - : config_reader_(new DnsConfigReader(this)), - hosts_reader_(new DnsHostsReader(this)) {} + : have_config_(false), + resolver_lib_(new ResolverLib()), + watcher_factory_(new FilePathWatcherFactory()) { +} DnsConfigServicePosix::~DnsConfigServicePosix() { DCHECK(CalledOnValidThread()); - config_reader_->Cancel(); - hosts_reader_->Cancel(); + if (watcher_delegate_.get()) + watcher_delegate_->Cancel(); +} + +void DnsConfigServicePosix::AddObserver(Observer* observer) { + DCHECK(CalledOnValidThread()); + observers_.AddObserver(observer); + if (have_config_) { + observer->OnConfigChanged(dns_config_); + } +} + +void DnsConfigServicePosix::RemoveObserver(Observer* observer) { + DCHECK(CalledOnValidThread()); + observers_.RemoveObserver(observer); } void DnsConfigServicePosix::Watch() { DCHECK(CalledOnValidThread()); - config_reader_->StartWatch(FilePath(FILE_PATH_LITERAL(_PATH_RESCONF))); - hosts_reader_->StartWatch(FilePath(FILE_PATH_LITERAL("/etc/hosts"))); + DCHECK(!watcher_delegate_.get()); + DCHECK(!resolv_file_watcher_.get()); + DCHECK(resolver_lib_.get()); + DCHECK(watcher_factory_.get()); + + watcher_delegate_ = new WatcherDelegate(this, resolver_lib_.release()); + StartWatch(); +} + +void DnsConfigServicePosix::OnConfigRead(const DnsConfig& config) { + DCHECK(CalledOnValidThread()); + if (!config.Equals(dns_config_)) { + dns_config_ = config; + have_config_ = true; + FOR_EACH_OBSERVER(Observer, observers_, OnConfigChanged(config)); + } +} + +void DnsConfigServicePosix::StartWatch() { + DCHECK(CalledOnValidThread()); + DCHECK(watcher_delegate_.get()); + + FilePath path(FILE_PATH_LITERAL(_PATH_RESCONF)); + + // FilePathWatcher allows only one Watch per lifetime, so we need a new one. + resolv_file_watcher_.reset(watcher_factory_->CreateFilePathWatcher()); + if (resolv_file_watcher_->Watch(path, watcher_delegate_.get())) { + // Make the initial read after watch is installed. + watcher_delegate_->OnFilePathChanged(path); + } else { + VLOG(1) << "Watch failed, scheduling restart"; + watcher_delegate_->RescheduleWatch(); + } +} + +int DnsConfigServicePosix::ResolverLib::ninit(res_state statp) { + return ::res_ninit(statp); +} + +void DnsConfigServicePosix::ResolverLib::nclose(res_state statp) { + return ::res_nclose(statp); +} + +DnsConfigServicePosix::FilePathWatcherShim::FilePathWatcherShim() + : watcher_(new base::files::FilePathWatcher()) {} +DnsConfigServicePosix::FilePathWatcherShim::~FilePathWatcherShim() {} + +bool DnsConfigServicePosix::FilePathWatcherShim::Watch( + const FilePath& path, + base::files::FilePathWatcher::Delegate* delegate) { + return watcher_->Watch(path, delegate); +} + +DnsConfigServicePosix::FilePathWatcherShim* +DnsConfigServicePosix::FilePathWatcherFactory::CreateFilePathWatcher() { + return new FilePathWatcherShim(); } // static diff --git a/net/dns/dns_config_service_posix.h b/net/dns/dns_config_service_posix.h index 5349adf..1cdd0b2 100644 --- a/net/dns/dns_config_service_posix.h +++ b/net/dns/dns_config_service_posix.h @@ -9,29 +9,102 @@ #include <resolv.h> #include "base/compiler_specific.h" +#include "base/files/file_path_watcher.h" #include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "base/threading/non_thread_safe.h" #include "net/base/net_export.h" #include "net/dns/dns_config_service.h" namespace net { class NET_EXPORT_PRIVATE DnsConfigServicePosix - : NON_EXPORTED_BASE(public DnsConfigService) { + : public NON_EXPORTED_BASE(DnsConfigService), + public NON_EXPORTED_BASE(base::NonThreadSafe) { public: - class DnsConfigReader; + class ResolverLib; + class FilePathWatcherShim; + class FilePathWatcherFactory; + class WatcherDelegate; DnsConfigServicePosix(); virtual ~DnsConfigServicePosix(); + virtual void AddObserver(Observer* observer) OVERRIDE; + virtual void RemoveObserver(Observer* observer) OVERRIDE; virtual void Watch() OVERRIDE; + // Takes ownership of |lib|. Must be set before Watch. + void set_resolver_lib(ResolverLib* lib) { + DCHECK(!watcher_delegate_.get()); + resolver_lib_.reset(lib); + } + + // Takes ownership of |factory|. Must be set before Watch. + void set_watcher_factory(FilePathWatcherFactory* factory) { + DCHECK(!watcher_delegate_.get()); + watcher_factory_.reset(factory); + } + private: - scoped_refptr<WatchingFileReader> config_reader_; - scoped_refptr<WatchingFileReader> hosts_reader_; + void OnConfigRead(const DnsConfig& config); + + // Configure a FilePathWatcher and install watcher_delegate_. Executed each + // time FilePathWatcher fails. + void StartWatch(); + + DnsConfig dns_config_; + // True after first OnConfigChanged, that is, dns_config_ is valid. + bool have_config_; + + scoped_ptr<ResolverLib> resolver_lib_; + scoped_ptr<FilePathWatcherFactory> watcher_factory_; + scoped_ptr<FilePathWatcherShim> resolv_file_watcher_; + scoped_refptr<WatcherDelegate> watcher_delegate_; + ObserverList<Observer> observers_; DISALLOW_COPY_AND_ASSIGN(DnsConfigServicePosix); }; + +// Allows mocking res_ninit. +class NET_EXPORT_PRIVATE DnsConfigServicePosix::ResolverLib + : public NON_EXPORTED_BASE(base::NonThreadSafe) { + public: + ResolverLib() {} + virtual ~ResolverLib() {} + virtual int ninit(res_state statp); + virtual void nclose(res_state statp); + private: + DISALLOW_COPY_AND_ASSIGN(ResolverLib); +}; + +// Allows mocking FilePathWatcher +class NET_EXPORT_PRIVATE DnsConfigServicePosix::FilePathWatcherShim + : public NON_EXPORTED_BASE(base::NonThreadSafe) { + public: + FilePathWatcherShim(); + virtual ~FilePathWatcherShim(); + + virtual bool Watch( + const FilePath& path, + base::files::FilePathWatcher::Delegate* delegate) WARN_UNUSED_RESULT; + private: + scoped_ptr<base::files::FilePathWatcher> watcher_; + DISALLOW_COPY_AND_ASSIGN(FilePathWatcherShim); +}; + +class NET_EXPORT_PRIVATE DnsConfigServicePosix::FilePathWatcherFactory + : public NON_EXPORTED_BASE(base::NonThreadSafe) { + public: + FilePathWatcherFactory() {} + virtual ~FilePathWatcherFactory() {} + virtual FilePathWatcherShim* CreateFilePathWatcher(); + private: + DISALLOW_COPY_AND_ASSIGN(FilePathWatcherFactory); +}; + // Fills in |dns_config| from |res|. Exposed for tests. bool NET_EXPORT_PRIVATE ConvertResToConfig(const struct __res_state& res, DnsConfig* dns_config); diff --git a/net/dns/dns_config_service_posix_unittest.cc b/net/dns/dns_config_service_posix_unittest.cc index a3f90aa..d38d589 100644 --- a/net/dns/dns_config_service_posix_unittest.cc +++ b/net/dns/dns_config_service_posix_unittest.cc @@ -5,8 +5,14 @@ #include <arpa/inet.h> #include <resolv.h> +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/message_loop.h" +#include "base/message_loop_proxy.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "net/base/ip_endpoint.h" #include "net/dns/dns_config_service_posix.h" - #include "testing/gtest/include/gtest/gtest.h" namespace net { @@ -108,18 +114,264 @@ void CloseResState(res_state res) { #endif } -} // namespace +class DnsConfigServiceTest : public testing::Test, + public DnsConfigService::Observer { + public: + // Mocks + + // DnsConfigService owns the instances of ResolverLib and + // FilePathWatcherFactory that it gets, so use simple proxies to call + // DnsConfigServiceTest. + + // ResolverLib is owned by WatcherDelegate which is posted to WorkerPool so + // it must be canceled before the test is over. + class MockResolverLib : public DnsConfigServicePosix::ResolverLib { + public: + explicit MockResolverLib(DnsConfigServiceTest *test) : test_(test) {} + virtual ~MockResolverLib() { + base::AutoLock lock(lock_); + if (test_) { + EXPECT_TRUE(test_->IsComplete()); + } + } + virtual int ninit(res_state res) OVERRIDE { + base::AutoLock lock(lock_); + if (test_) + return test_->OnNinit(res); + else + return -1; + } + virtual void nclose(res_state res) OVERRIDE { + CloseResState(res); + } + void Cancel() { + base::AutoLock lock(lock_); + test_ = NULL; + } + private: + base::Lock lock_; + DnsConfigServiceTest *test_; + }; + + class MockFilePathWatcherShim + : public DnsConfigServicePosix::FilePathWatcherShim { + public: + typedef base::files::FilePathWatcher::Delegate Delegate; + + explicit MockFilePathWatcherShim(DnsConfigServiceTest* t) : test_(t) {} + virtual ~MockFilePathWatcherShim() { + test_->OnShimDestroyed(this); + } + + // Enforce one-Watch-per-lifetime as the original FilePathWatcher + virtual bool Watch(const FilePath& path, + Delegate* delegate) OVERRIDE { + EXPECT_TRUE(path_.empty()) << "Only one-Watch-per-lifetime allowed."; + EXPECT_TRUE(!delegate_.get()); + path_ = path; + delegate_ = delegate; + return test_->OnWatch(); + } + + void PathChanged() { + delegate_->OnFilePathChanged(path_); + } + + void PathError() { + delegate_->OnFilePathError(path_); + } + + private: + FilePath path_; + scoped_refptr<Delegate> delegate_; + DnsConfigServiceTest* test_; + }; + + class MockFilePathWatcherFactory + : public DnsConfigServicePosix::FilePathWatcherFactory { + public: + explicit MockFilePathWatcherFactory(DnsConfigServiceTest* t) : test(t) {} + virtual ~MockFilePathWatcherFactory() { + EXPECT_TRUE(test->IsComplete()); + } + virtual DnsConfigServicePosix::FilePathWatcherShim* + CreateFilePathWatcher() OVERRIDE { + return test->CreateFilePathWatcher(); + } + DnsConfigServiceTest* test; + }; + + // Helpers for mocks. + + DnsConfigServicePosix::FilePathWatcherShim* CreateFilePathWatcher() { + watcher_shim_ = new MockFilePathWatcherShim(this); + return watcher_shim_; + } + + void OnShimDestroyed(MockFilePathWatcherShim* destroyed_shim) { + // Precaution to avoid segfault. + if (watcher_shim_ == destroyed_shim) + watcher_shim_ = NULL; + } + + // On each event, post QuitTask to allow use of MessageLoop::Run() to + // synchronize the threads. + + bool OnWatch() { + EXPECT_TRUE(message_loop_ == MessageLoop::current()); + watch_called_ = true; + BreakNow("OnWatch"); + return !fail_on_watch_; + } + + int OnNinit(res_state res) { + { // Check that res_ninit is executed serially. + base::AutoLock lock(ninit_lock_); + EXPECT_FALSE(ninit_running_) << "res_ninit is not called serially!"; + ninit_running_ = true; + } + BreakNow("OnNinit"); + ninit_allowed_.Wait(); + // Calling from another thread is a bit dirty, but it's protected. + int rv = OnNinitNonThreadSafe(res); + // This lock might be destroyed after ninit_called_ is signalled. + { + base::AutoLock lock(ninit_lock_); + ninit_running_ = false; + } + ninit_called_.Signal(); + return rv; + } + + virtual void OnConfigChanged(const DnsConfig& new_config) OVERRIDE { + EXPECT_TRUE(message_loop_ == MessageLoop::current()); + CompareConfig(res_, new_config); + EXPECT_FALSE(new_config.Equals(last_config_)) << + "Config must be different from last call."; + last_config_ = new_config; + got_config_ = true; + BreakNow("OnConfigChanged"); + } + + bool IsComplete() { + return complete_; + } + + protected: + friend class BreakTask; + class BreakTask : public Task { + public: + BreakTask(DnsConfigServiceTest* test, std::string breakpoint) + : test_(test), breakpoint_(breakpoint) {} + virtual ~BreakTask() {} + virtual void Run() OVERRIDE { + test_->breakpoint_ = breakpoint_; + MessageLoop::current()->QuitNow(); + } + private: + DnsConfigServiceTest* test_; + std::string breakpoint_; + }; + + void BreakNow(std::string b) { + message_loop_->PostTask(FROM_HERE, new BreakTask(this, b)); + } + + void RunUntilBreak(std::string b) { + message_loop_->Run(); + ASSERT_EQ(breakpoint_, b); + } + + DnsConfigServiceTest() + : res_lib_(new MockResolverLib(this)), + watcher_shim_(NULL), + res_generation_(1), + watch_called_(false), + got_config_(false), + fail_on_watch_(false), + fail_on_ninit_(false), + complete_(false), + ninit_allowed_(false, false), + ninit_called_(false, false), + ninit_running_(false) { + } + + // This is on WorkerPool, but protected by ninit_allowed_. + int OnNinitNonThreadSafe(res_state res) { + if (fail_on_ninit_) + return -1; + InitializeResState(res, res_generation_); + // Store a (deep) copy in the fixture to later verify correctness. + CloseResState(&res_); + InitializeResState(&res_, res_generation_); + return 0; + } + + // Helpers for tests. + + // Lets OnNinit run OnNinitNonThreadSafe and waits for it to complete. + // Might get OnConfigChanged scheduled on the loop but we have no certainty. + void WaitForNinit() { + RunUntilBreak("OnNinit"); + ninit_allowed_.Signal(); + ninit_called_.Wait(); + } + + // test::Test methods + virtual void SetUp() OVERRIDE { + message_loop_ = MessageLoop::current(); + service_.reset(new DnsConfigServicePosix()); + service_->set_resolver_lib(res_lib_); + service_->set_watcher_factory(new MockFilePathWatcherFactory(this)); + memset(&res_, 0, sizeof(res_)); + } + + virtual void TearDown() OVERRIDE { + // res_lib_ could outlive the test, so make sure it doesn't call it. + res_lib_->Cancel(); + CloseResState(&res_); + // Allow service_ to clean up ResolverLib and FilePathWatcherFactory. + complete_ = true; + } + + MockResolverLib* res_lib_; + MockFilePathWatcherShim* watcher_shim_; + // Adds variety to the content of res_state. + int res_generation_; + struct __res_state res_; + DnsConfig last_config_; + + bool watch_called_; + bool got_config_; + bool fail_on_watch_; + bool fail_on_ninit_; + bool complete_; + + // Ninit is called on WorkerPool so we need to synchronize with it. + base::WaitableEvent ninit_allowed_; + base::WaitableEvent ninit_called_; + + // Protected by ninit_lock_. Used to verify that Ninit calls are serialized. + bool ninit_running_; + base::Lock ninit_lock_; + + // Loop for this thread. + MessageLoop* message_loop_; + + // Service under test. + scoped_ptr<DnsConfigServicePosix> service_; + + std::string breakpoint_; +}; TEST(DnsConfigTest, ResolverConfigConvertAndEquals) { struct __res_state res[2]; DnsConfig config[2]; for (int i = 0; i < 2; ++i) { - EXPECT_FALSE(config[i].IsValid()); InitializeResState(&res[i], i); ASSERT_TRUE(ConvertResToConfig(res[i], &config[i])); } for (int i = 0; i < 2; ++i) { - EXPECT_TRUE(config[i].IsValid()); CompareConfig(res[i], config[i]); CloseResState(&res[i]); } @@ -128,6 +380,93 @@ TEST(DnsConfigTest, ResolverConfigConvertAndEquals) { EXPECT_FALSE(config[1].Equals(config[0])); } -} // namespace net +TEST_F(DnsConfigServiceTest, FilePathWatcherFailures) { + // For these tests, disable ninit. + res_lib_->Cancel(); + + fail_on_watch_ = true; + service_->Watch(); + RunUntilBreak("OnWatch"); + EXPECT_TRUE(watch_called_) << "Must call FilePathWatcher::Watch()."; + + fail_on_watch_ = false; + watch_called_ = false; + RunUntilBreak("OnWatch"); // Due to backoff this will take 100ms. + EXPECT_TRUE(watch_called_) << + "Must restart on FilePathWatcher::Watch() failure."; + + watch_called_ = false; + ASSERT_TRUE(watcher_shim_); + watcher_shim_->PathError(); + RunUntilBreak("OnWatch"); + EXPECT_TRUE(watch_called_) << + "Must restart on FilePathWatcher::Delegate::OnFilePathError()."; + // Worker thread could still be posting OnResultAvailable to the message loop +} + +TEST_F(DnsConfigServiceTest, NotifyOnValidAndDistinctConfig) { + service_->AddObserver(this); + service_->Watch(); + RunUntilBreak("OnWatch"); + fail_on_ninit_ = true; + WaitForNinit(); + + // If OnNinit posts OnResultAvailable before the next call, then this test + // verifies that failure on ninit should not cause OnConfigChanged. + // Otherwise, this only verifies that ninit calls are serialized. + + fail_on_ninit_ = false; + ASSERT_TRUE(watcher_shim_); + watcher_shim_->PathChanged(); + WaitForNinit(); + + RunUntilBreak("OnConfigChanged"); + EXPECT_TRUE(got_config_); + + message_loop_->AssertIdle(); + + got_config_ = false; + // Forget about the config to test if we get it again on AddObserver. + last_config_ = DnsConfig(); + service_->RemoveObserver(this); + service_->AddObserver(this); + RunUntilBreak("OnConfigChanged"); + EXPECT_TRUE(got_config_) << "Did not get config after AddObserver."; + + // Simulate spurious FilePathChanged. + ASSERT_TRUE(watcher_shim_); + watcher_shim_->PathChanged(); + WaitForNinit(); + + // OnConfigChanged will catch that the config did not actually change. + + got_config_ = false; + ++res_generation_; + ASSERT_TRUE(watcher_shim_); + watcher_shim_->PathChanged(); + WaitForNinit(); + RunUntilBreak("OnConfigChanged"); + EXPECT_TRUE(got_config_) << "Did not get config after change"; + + message_loop_->AssertIdle(); + + // Schedule two calls. OnNinit checks if it is called serially. + ++res_generation_; + ASSERT_TRUE(watcher_shim_); + watcher_shim_->PathChanged(); + // ninit is blocked, so this will have to induce read_pending + watcher_shim_->PathChanged(); + WaitForNinit(); + WaitForNinit(); + RunUntilBreak("OnConfigChanged"); + EXPECT_TRUE(got_config_) << "Did not get config after change"; + + // We should be done with all tasks. + message_loop_->AssertIdle(); +} + +} // namespace + +} // namespace net diff --git a/net/dns/dns_config_service_unittest.cc b/net/dns/dns_config_service_unittest.cc deleted file mode 100644 index 5500e70..0000000 --- a/net/dns/dns_config_service_unittest.cc +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "net/dns/dns_config_service.h" - -#include "base/compiler_specific.h" -#include "base/message_loop.h" -#include "base/task.h" -#include "base/test/test_timeouts.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace net { - -namespace { - -class DnsConfigServiceTest : public testing::Test, - public DnsConfigService::Observer { - public: - void OnConfigChanged(const DnsConfig& config) OVERRIDE { - EXPECT_FALSE(config.Equals(last_config_)) << - "Config must be different from last call."; - last_config_ = config; - if (quit_on_config_) - MessageLoop::current()->Quit(); - } - void Timeout() const { - MessageLoop::current()->Quit(); - } - - protected: - // test::Test methods - virtual void SetUp() OVERRIDE { - service_.reset(new DnsConfigService()); - quit_on_config_ = false; - } - - DnsConfig last_config_; - bool quit_on_config_; - - // Service under test. - scoped_ptr<DnsConfigService> service_; -}; - -} // namespace - -TEST_F(DnsConfigServiceTest, NotifyOnChange) { - service_->AddObserver(this); - service_->Watch(); - - DnsConfig config; - IPAddressNumber ip; - ASSERT_TRUE(ParseIPLiteralToNumber("1.2.3.4", &ip)); - config.nameservers.push_back(IPEndPoint(ip, 53)); - ASSERT_TRUE(config.IsValid()); - service_->OnConfigRead(config); - - EXPECT_FALSE(last_config_.Equals(config)) << - "Unexpected notification with incomplete config."; - - DnsHosts hosts; - ParseHosts("127.0.0.1 localhost", &hosts); - ASSERT_FALSE(hosts.empty()); - service_->OnHostsRead(hosts); - - EXPECT_TRUE(last_config_.hosts == hosts); - EXPECT_TRUE(last_config_.EqualsIgnoreHosts(config)); - - DnsConfig complete_config = config; - complete_config.hosts = hosts; - EXPECT_TRUE(last_config_.Equals(complete_config)); - - // Check if it ignores no-change in config or hosts. - service_->OnConfigRead(config); - service_->OnHostsRead(hosts); - - // Check if it provides config and hosts after AddObserver; - service_->RemoveObserver(this); - last_config_ = DnsConfig(); - service_->AddObserver(this); - EXPECT_TRUE(last_config_.Equals(complete_config)); -} - -#if defined(OS_POSIX) -// TODO(szym): enable OS_WIN once ready -// This is really an integration test. -TEST_F(DnsConfigServiceTest, GetSystemConfig) { - DnsConfigService* service = DnsConfigService::CreateSystemService(); - - // Quit the loop after timeout unless cancelled - const int64 kTimeout = TestTimeouts::action_timeout_ms(); - ScopedRunnableMethodFactory<DnsConfigServiceTest> factory_(this); - MessageLoop::current()->PostDelayedTask( - FROM_HERE, - factory_.NewRunnableMethod(&DnsConfigServiceTest::Timeout), - kTimeout); - - service->AddObserver(this); - service->Watch(); - quit_on_config_ = true; - MessageLoop::current()->Run(); - ASSERT_TRUE(last_config_.IsValid()) << "Did not receive DnsConfig in " << - kTimeout << "ms"; -} -#endif // OS_POSIX - -} // namespace net - diff --git a/net/dns/dns_hosts.cc b/net/dns/dns_hosts.cc deleted file mode 100644 index 1342808..0000000 --- a/net/dns/dns_hosts.cc +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "net/dns/dns_hosts.h" - -#include "base/logging.h" -#include "base/string_tokenizer.h" - -namespace net { - -void ParseHosts(const std::string& contents, DnsHosts* dns_hosts) { - CHECK(dns_hosts); - DnsHosts& hosts = *dns_hosts; - // Split into lines. Accept CR for Windows. - StringTokenizer contents_lines(contents, "\n\r"); - while (contents_lines.GetNext()) { - // Ignore comments after '#'. - std::string line = contents_lines.token(); - StringTokenizer line_parts(line, "#"); - line_parts.set_options(StringTokenizer::RETURN_DELIMS); - - if (line_parts.GetNext() && !line_parts.token_is_delim()) { - // Split and trim whitespace. - std::string part = line_parts.token(); - StringTokenizer tokens(part, " \t"); - - if (tokens.GetNext()) { - IPAddressNumber ip; - // TODO(szym): handle %iface notation on mac - if (!ParseIPLiteralToNumber(tokens.token(), &ip)) - continue; // Ignore malformed lines. - AddressFamily fam = (ip.size() == 4) ? ADDRESS_FAMILY_IPV4 : - ADDRESS_FAMILY_IPV6; - while (tokens.GetNext()) { - IPAddressNumber& mapped_ip = hosts[DnsHostsKey(tokens.token(), fam)]; - if (mapped_ip.empty()) - mapped_ip = ip; - // else ignore this entry (first hit counts) - } - } - } - } -} - -} // namespace net - diff --git a/net/dns/dns_hosts.h b/net/dns/dns_hosts.h deleted file mode 100644 index fda7733..0000000 --- a/net/dns/dns_hosts.h +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef NET_DNS_DNS_HOSTS_H_ -#define NET_DNS_DNS_HOSTS_H_ -#pragma once - -#include <map> -#include <string> -#include <utility> -#include <vector> - -#include "net/base/address_family.h" -#include "net/base/net_export.h" -#include "net/base/net_util.h" // can't forward-declare IPAddressNumber - -namespace net { - -// Parsed results of a Hosts file. -// -// Although Hosts files map IP address to a list of domain names, for name -// resolution the desired mapping direction is: domain name to IP address. -// When parsing Hosts, we apply the "first hit" rule as Windows and glibc do. -// With a Hosts file of: -// 300.300.300.300 localhost # bad ip -// 127.0.0.1 localhost -// 10.0.0.1 localhost -// The expected resolution of localhost is 127.0.0.1. -typedef std::pair<std::string, AddressFamily> DnsHostsKey; -typedef std::map<DnsHostsKey, IPAddressNumber> DnsHosts; - -// Parses |contents| (as read from /etc/hosts or equivalent) and stores results -// in |dns_hosts|. Invalid lines are ignored (as in most implementations). -void NET_EXPORT_PRIVATE ParseHosts(const std::string& contents, - DnsHosts* dns_hosts); - -} // namespace net - -#endif // NET_DNS_DNS_HOSTS_H_ - diff --git a/net/dns/dns_hosts_unittest.cc b/net/dns/dns_hosts_unittest.cc deleted file mode 100644 index 6da41a9..0000000 --- a/net/dns/dns_hosts_unittest.cc +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "net/dns/dns_hosts.h" - -#include "testing/gtest/include/gtest/gtest.h" - -namespace net { - -namespace { - -TEST(DnsHostsTest, ParseHosts) { - std::string contents = - "127.0.0.1 localhost\tlocalhost.localdomain # standard\n" - "\n" - "1.0.0.1 localhost # ignored, first hit above\n" - "fe00::x example company # ignored, malformed IPv6\n" - "1.0.0.300 company # ignored, malformed IPv4\n" - "1.0.0.1 # ignored, missing hostname\n" - "1.0.0.1\t company \n" - "::1\tlocalhost ip6-localhost ip6-loopback # comment # within a comment\n" - "\t fe00::0 ip6-localnet\r\n" - "2048::2 example\n" - "2048::1 company example # ignored for 'example' \n" - "gibberish"; - - const struct { - const char* host; - AddressFamily family; - const char* ip; - } entries[] = { - { "localhost", ADDRESS_FAMILY_IPV4, "127.0.0.1" }, - { "localhost.localdomain", ADDRESS_FAMILY_IPV4, "127.0.0.1" }, - { "company", ADDRESS_FAMILY_IPV4, "1.0.0.1" }, - { "localhost", ADDRESS_FAMILY_IPV6, "::1" }, - { "ip6-localhost", ADDRESS_FAMILY_IPV6, "::1" }, - { "ip6-loopback", ADDRESS_FAMILY_IPV6, "::1" }, - { "ip6-localnet", ADDRESS_FAMILY_IPV6, "fe00::0" }, - { "company", ADDRESS_FAMILY_IPV6, "2048::1" }, - { "example", ADDRESS_FAMILY_IPV6, "2048::2" }, - }; - - DnsHosts expected; - for (size_t i = 0; i < ARRAYSIZE_UNSAFE(entries); ++i) { - DnsHostsKey key(entries[i].host, entries[i].family); - IPAddressNumber& ip = expected[key]; - ASSERT_TRUE(ip.empty()); - ASSERT_TRUE(ParseIPLiteralToNumber(entries[i].ip, &ip)); - ASSERT_EQ(ip.size(), (entries[i].family == ADDRESS_FAMILY_IPV4) ? 4u : 16u); - } - - DnsHosts hosts; - ParseHosts(contents, &hosts); - ASSERT_EQ(expected, hosts); -} - -} // namespace - -} // namespace net - diff --git a/net/dns/watching_file_reader.cc b/net/dns/watching_file_reader.cc deleted file mode 100644 index b5f9159..0000000 --- a/net/dns/watching_file_reader.cc +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "net/dns/watching_file_reader.h" - -#include "base/bind.h" -#include "base/message_loop_proxy.h" -#include "base/threading/worker_pool.h" - -namespace net { - -FilePathWatcherShim::FilePathWatcherShim() - : watcher_(new base::files::FilePathWatcher()) {} -FilePathWatcherShim::~FilePathWatcherShim() {} - -bool FilePathWatcherShim::Watch( - const FilePath& path, - base::files::FilePathWatcher::Delegate* delegate) { - DCHECK(CalledOnValidThread()); - return watcher_->Watch(path, delegate); -} - -FilePathWatcherShim* -FilePathWatcherFactory::CreateFilePathWatcher() { - DCHECK(CalledOnValidThread()); - return new FilePathWatcherShim(); -} - -WatchingFileReader::WatchingFileReader() - : message_loop_(base::MessageLoopProxy::current()), - factory_(new FilePathWatcherFactory()), - reading_(false), - read_pending_(false), - cancelled_(false) {} - -void WatchingFileReader::StartWatch(const FilePath& path) { - DCHECK(message_loop_->BelongsToCurrentThread()); - DCHECK(!watcher_.get()); - DCHECK(path_.empty()); - path_ = path; - RestartWatch(); -} - -void WatchingFileReader::Cancel() { - DCHECK(message_loop_->BelongsToCurrentThread()); - cancelled_ = true; - // Let go of the watcher to break the reference cycle. - watcher_.reset(); -} - -void WatchingFileReader::OnFilePathChanged(const FilePath& path) { - DCHECK(message_loop_->BelongsToCurrentThread()); - ReadNow(); -} - -void WatchingFileReader::OnFilePathError(const FilePath& path) { - DCHECK(message_loop_->BelongsToCurrentThread()); - RestartWatch(); -} - -WatchingFileReader::~WatchingFileReader() {} - -void WatchingFileReader::RescheduleWatch() { - DCHECK(message_loop_->BelongsToCurrentThread()); - message_loop_->PostDelayedTask( - FROM_HERE, - base::Bind(&WatchingFileReader::RestartWatch, this), - kWatchRetryDelayMs); -} - -void WatchingFileReader::RestartWatch() { - if (cancelled_) - return; - watcher_.reset(factory_->CreateFilePathWatcher()); - if (watcher_->Watch(path_, this)) { - ReadNow(); - } else { - LOG(WARNING) << "Watch on " << - path_.LossyDisplayName() << - " failed, scheduling restart"; - RescheduleWatch(); - } -} - -void WatchingFileReader::ReadNow() { - if (cancelled_) - return; - if (reading_) { - // Remember to re-read after DoRead posts results. - read_pending_ = true; - } else { - if (!base::WorkerPool::PostTask(FROM_HERE, base::Bind( - &WatchingFileReader::DoReadJob, this), false)) { -#if defined(OS_POSIX) - // See worker_pool_posix.cc. - NOTREACHED() << "WorkerPool::PostTask is not expected to fail on posix"; -#else - LOG(WARNING) << "Failed to WorkerPool::PostTask, will retry later"; - message_loop_->PostDelayedTask( - FROM_HERE, - base::Bind(&WatchingFileReader::ReadNow, this), - kWorkerPoolRetryDelayMs); - return; -#endif - } - reading_ = true; - read_pending_ = false; - } -} - -void WatchingFileReader::DoReadJob() { - this->DoRead(); - // If this fails, the loop is gone, so there is no point retrying. - message_loop_->PostTask(FROM_HERE, base::Bind( - &WatchingFileReader::OnReadJobFinished, this)); -} - -void WatchingFileReader::OnReadJobFinished() { - DCHECK(message_loop_->BelongsToCurrentThread()); - if (cancelled_) - return; - reading_ = false; - if (read_pending_) { - // Discard this result and re-read. - ReadNow(); - return; - } - this->OnReadFinished(); -} - -} // namespace net - diff --git a/net/dns/watching_file_reader.h b/net/dns/watching_file_reader.h deleted file mode 100644 index 77a9f12..0000000 --- a/net/dns/watching_file_reader.h +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef NET_DNS_WATCHING_FILE_READER_H_ -#define NET_DNS_WATCHING_FILE_READER_H_ -#pragma once - -#include <string> - -#include "base/compiler_specific.h" -#include "base/files/file_path_watcher.h" -#include "base/memory/scoped_ptr.h" -#include "base/threading/non_thread_safe.h" -#include "net/base/net_export.h" - -// Forward declaration -namespace base { -class MessageLoopProxy; -} - -namespace net { - -// Allows mocking FilePathWatcher -class NET_EXPORT_PRIVATE FilePathWatcherShim - : NON_EXPORTED_BASE(public base::NonThreadSafe) { - public: - FilePathWatcherShim(); - virtual ~FilePathWatcherShim(); - - virtual bool Watch( - const FilePath& path, - base::files::FilePathWatcher::Delegate* delegate) WARN_UNUSED_RESULT; - private: - scoped_ptr<base::files::FilePathWatcher> watcher_; - DISALLOW_COPY_AND_ASSIGN(FilePathWatcherShim); -}; - -class NET_EXPORT_PRIVATE FilePathWatcherFactory - : NON_EXPORTED_BASE(public base::NonThreadSafe) { - public: - FilePathWatcherFactory() {} - virtual ~FilePathWatcherFactory() {} - virtual FilePathWatcherShim* CreateFilePathWatcher(); - private: - DISALLOW_COPY_AND_ASSIGN(FilePathWatcherFactory); -}; - -// A FilePathWatcher::Delegate that restarts Watch on failures and executes -// DoRead on WorkerPool (one at a time) in response to FilePathChanged. -// Derived classes should store results of the work done in DoRead in dedicated -// fields and read them in OnReadFinished. -// -// This implementation avoids locking by using the reading_ flag to ensure that -// DoRead and OnReadFinished cannot execute in parallel. -// -// There's no need to call Cancel() on destruction. On the contrary, a ref-cycle -// between the WatchingFileReader and a FilePathWatcher will prevent destruction -// until Cancel() is called. -// -// TODO(szym): update to WorkerPool::PostTaskAndReply once available. -class NET_EXPORT_PRIVATE WatchingFileReader - : public base::files::FilePathWatcher::Delegate { - public: - WatchingFileReader(); - - // Must be called at most once. Made virtual to allow mocking. - virtual void StartWatch(const FilePath& path); - - // Unless already scheduled, post DoRead to WorkerPool. - void ReadNow(); - - // Do not perform any further work, except perhaps a latent DoRead(). - // Always call it once done with the delegate to ensure destruction. - void Cancel(); - - void set_watcher_factory(FilePathWatcherFactory* factory) { - DCHECK(!watcher_.get()); - factory_.reset(factory); - } - - FilePath get_path() const { - return path_; - } - - // Delay between calls to FilePathWatcher::Watch. - static const int kWatchRetryDelayMs = 100; - - // Delay between calls to WorkerPool::PostTask - static const int kWorkerPoolRetryDelayMs = 100; - - protected: - // protected to allow sub-classing, but prevent deleting - virtual ~WatchingFileReader(); - - // Executed on WorkerPool, at most once at a time. - virtual void DoRead() = 0; - // Executed on origin thread after DoRead() completes. - virtual void OnReadFinished() = 0; - - private: - // FilePathWatcher::Delegate interface - virtual void OnFilePathChanged(const FilePath& path) OVERRIDE; - virtual void OnFilePathError(const FilePath& path) OVERRIDE; - - void RescheduleWatch(); - void RestartWatch(); - - // Called on the worker thread, executes DoRead and notifies the origin - // thread. - void DoReadJob(); - - // Called on the the origin thread after DoRead completes. - void OnReadJobFinished(); - - // Message loop for the thread on which watcher is used (of TYPE_IO). - scoped_refptr<base::MessageLoopProxy> message_loop_; - FilePath path_; - scoped_ptr<FilePathWatcherFactory> factory_; - scoped_ptr<FilePathWatcherShim> watcher_; - // True after DoRead before OnResultsAvailable. - bool reading_; - // True after OnFilePathChanged fires while |reading_| is true. - bool read_pending_; - bool cancelled_; - - DISALLOW_COPY_AND_ASSIGN(WatchingFileReader); -}; - -} // namespace net - -#endif // NET_DNS_WATCHING_FILE_READER_H_ - diff --git a/net/dns/watching_file_reader_unittest.cc b/net/dns/watching_file_reader_unittest.cc deleted file mode 100644 index 24021f0..0000000 --- a/net/dns/watching_file_reader_unittest.cc +++ /dev/null @@ -1,289 +0,0 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "net/dns/watching_file_reader.h" - -#include "base/message_loop.h" -#include "base/synchronization/lock.h" -#include "base/synchronization/waitable_event.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace net { - -namespace { - -class WatchingFileReaderTest : public testing::Test { - public: - - // The class under test - class TestWatchingFileReader : public WatchingFileReader { - public: - explicit TestWatchingFileReader(WatchingFileReaderTest* t) - : test_(t) {} - virtual void DoRead() OVERRIDE { - ASSERT_TRUE(test_); - test_->OnRead(); - } - virtual void OnReadFinished() OVERRIDE { - ASSERT_TRUE(test_); - test_->OnReadFinished(); - } - void TestCancel() { - // Could execute concurrently with DoRead() - test_ = NULL; - } - private: - virtual ~TestWatchingFileReader() {} - WatchingFileReaderTest* test_; - base::Lock lock_; - }; - - // Mocks - - // WatcherDelegate owns the FilePathWatcherFactory it gets, so use simple - // proxies to call the test fixture. - - class MockFilePathWatcherShim - : public FilePathWatcherShim { - public: - typedef base::files::FilePathWatcher::Delegate Delegate; - - explicit MockFilePathWatcherShim(WatchingFileReaderTest* t) : test_(t) {} - virtual ~MockFilePathWatcherShim() { - test_->OnShimDestroyed(this); - } - - // Enforce one-Watch-per-lifetime as the original FilePathWatcher - virtual bool Watch(const FilePath& path, - Delegate* delegate) OVERRIDE { - EXPECT_TRUE(path_.empty()) << "Only one-Watch-per-lifetime allowed."; - EXPECT_TRUE(!delegate_.get()); - path_ = path; - delegate_ = delegate; - return test_->OnWatch(); - } - - void PathChanged() { - delegate_->OnFilePathChanged(path_); - } - - void PathError() { - delegate_->OnFilePathError(path_); - } - - private: - FilePath path_; - scoped_refptr<Delegate> delegate_; - WatchingFileReaderTest* test_; - }; - - class MockFilePathWatcherFactory - : public FilePathWatcherFactory { - public: - explicit MockFilePathWatcherFactory(WatchingFileReaderTest* t) - : test(t) {} - virtual FilePathWatcherShim* - CreateFilePathWatcher() OVERRIDE { - return test->CreateFilePathWatcher(); - } - WatchingFileReaderTest* test; - }; - - // Helpers for mocks. - - FilePathWatcherShim* CreateFilePathWatcher() { - watcher_shim_ = new MockFilePathWatcherShim(this); - return watcher_shim_; - } - - void OnShimDestroyed(MockFilePathWatcherShim* destroyed_shim) { - // Precaution to avoid segfault. - if (watcher_shim_ == destroyed_shim) - watcher_shim_ = NULL; - } - - // On each event, post QuitTask to allow use of MessageLoop::Run() to - // synchronize the threads. - - bool OnWatch() { - EXPECT_TRUE(message_loop_ == MessageLoop::current()); - BreakNow("OnWatch"); - return !fail_on_watch_; - } - - void OnRead() { - { // Check that OnRead is executed serially. - base::AutoLock lock(read_lock_); - EXPECT_FALSE(read_running_) << "DoRead is not called serially!"; - read_running_ = true; - } - BreakNow("OnRead"); - read_allowed_.Wait(); - // Calling from WorkerPool, but protected by read_allowed_/read_called_. - output_value_ = input_value_; - - { // This lock might be destroyed after read_called_ is signalled. - base::AutoLock lock(read_lock_); - read_running_ = false; - } - read_called_.Signal(); - } - - void OnReadFinished() { - EXPECT_TRUE(message_loop_ == MessageLoop::current()); - EXPECT_EQ(output_value_, input_value_); - BreakNow("OnReadFinished"); - } - - protected: - friend class BreakTask; - class BreakTask : public Task { - public: - BreakTask(WatchingFileReaderTest* test, std::string breakpoint) - : test_(test), breakpoint_(breakpoint) {} - virtual ~BreakTask() {} - virtual void Run() OVERRIDE { - test_->breakpoint_ = breakpoint_; - MessageLoop::current()->QuitNow(); - } - private: - WatchingFileReaderTest* test_; - std::string breakpoint_; - }; - - void BreakNow(std::string b) { - message_loop_->PostTask(FROM_HERE, new BreakTask(this, b)); - } - - void RunUntilBreak(std::string b) { - message_loop_->Run(); - ASSERT_EQ(breakpoint_, b); - } - - WatchingFileReaderTest() - : watcher_shim_(NULL), - fail_on_watch_(false), - input_value_(0), - output_value_(-1), - read_allowed_(false, false), - read_called_(false, false), - read_running_(false) { - } - - // Helpers for tests. - - // Lets OnRead run and waits for it to complete. Can only return if OnRead is - // executed on a concurrent thread. - void WaitForRead() { - RunUntilBreak("OnRead"); - read_allowed_.Signal(); - read_called_.Wait(); - } - - // test::Test methods - virtual void SetUp() OVERRIDE { - message_loop_ = MessageLoop::current(); - watcher_ = new TestWatchingFileReader(this); - watcher_->set_watcher_factory(new MockFilePathWatcherFactory(this)); - } - - virtual void TearDown() OVERRIDE { - // Cancel the watcher to catch if it makes a late DoRead call. - watcher_->TestCancel(); - watcher_->Cancel(); - // Check if OnRead is stalled. - EXPECT_FALSE(read_running_) << "OnRead should be done by TearDown"; - // Release it for cleanliness. - if (read_running_) { - WaitForRead(); - } - } - - MockFilePathWatcherShim* watcher_shim_; - - bool fail_on_watch_; - - // Input value read on WorkerPool. - int input_value_; - // Output value written on WorkerPool. - int output_value_; - - // read is called on WorkerPool so we need to synchronize with it. - base::WaitableEvent read_allowed_; - base::WaitableEvent read_called_; - - // Protected by read_lock_. Used to verify that read calls are serialized. - bool read_running_; - base::Lock read_lock_; - - // Loop for this thread. - MessageLoop* message_loop_; - - // WatcherDelegate under test. - scoped_refptr<TestWatchingFileReader> watcher_; - - std::string breakpoint_; -}; - -TEST_F(WatchingFileReaderTest, FilePathWatcherFailures) { - fail_on_watch_ = true; - watcher_->StartWatch(FilePath(FILE_PATH_LITERAL("some_file_name"))); - RunUntilBreak("OnWatch"); - - fail_on_watch_ = false; - RunUntilBreak("OnWatch"); // Due to backoff this will take 100ms. - WaitForRead(); - RunUntilBreak("OnReadFinished"); - - ASSERT_TRUE(watcher_shim_); - watcher_shim_->PathError(); - RunUntilBreak("OnWatch"); - WaitForRead(); - RunUntilBreak("OnReadFinished"); - - message_loop_->AssertIdle(); -} - -TEST_F(WatchingFileReaderTest, ExecuteAndSerializeReads) { - watcher_->StartWatch(FilePath(FILE_PATH_LITERAL("some_file_name"))); - RunUntilBreak("OnWatch"); - WaitForRead(); - RunUntilBreak("OnReadFinished"); - - message_loop_->AssertIdle(); - - ++input_value_; - ASSERT_TRUE(watcher_shim_); - watcher_shim_->PathChanged(); - WaitForRead(); - RunUntilBreak("OnReadFinished"); - - message_loop_->AssertIdle(); - - ++input_value_; - ASSERT_TRUE(watcher_shim_); - watcher_shim_->PathChanged(); - WaitForRead(); - RunUntilBreak("OnReadFinished"); - - message_loop_->AssertIdle(); - - // Schedule two calls. OnRead checks if it is called serially. - ++input_value_; - ASSERT_TRUE(watcher_shim_); - watcher_shim_->PathChanged(); - // read is blocked, so this will have to induce read_pending - watcher_shim_->PathChanged(); - WaitForRead(); - WaitForRead(); - RunUntilBreak("OnReadFinished"); - - // No more tasks should remain. - message_loop_->AssertIdle(); -} - -} // namespace - -} // namespace net - diff --git a/net/net.gyp b/net/net.gyp index a551c5e..73b6d62 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -303,16 +303,12 @@ 'dns/dns_config_service.h', 'dns/dns_config_service_posix.cc', 'dns/dns_config_service_posix.h', - 'dns/dns_hosts.cc', - 'dns/dns_hosts.h', 'dns/dns_query.cc', 'dns/dns_query.h', 'dns/dns_response.cc', 'dns/dns_response.h', 'dns/dns_transaction.cc', 'dns/dns_transaction.h', - 'dns/watching_file_reader.cc', - 'dns/watching_file_reader.h', 'ftp/ftp_auth_cache.cc', 'ftp/ftp_auth_cache.h', 'ftp/ftp_ctrl_response_buffer.cc', @@ -972,12 +968,9 @@ 'disk_cache/storage_block_unittest.cc', 'dns/async_host_resolver_unittest.cc', 'dns/dns_config_service_posix_unittest.cc', - 'dns/dns_config_service_unittest.cc', - 'dns/dns_hosts_unittest.cc', 'dns/dns_query_unittest.cc', 'dns/dns_response_unittest.cc', 'dns/dns_transaction_unittest.cc', - 'dns/watching_file_reader_unittest.cc', 'ftp/ftp_auth_cache_unittest.cc', 'ftp/ftp_ctrl_response_buffer_unittest.cc', 'ftp/ftp_directory_listing_parser_ls_unittest.cc', |