diff options
author | mmenke@chromium.org <mmenke@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-05 17:08:13 +0000 |
---|---|---|
committer | mmenke@chromium.org <mmenke@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-05 17:08:13 +0000 |
commit | aab6693524a5303a9c41e5cd4f45808c82a3b01c (patch) | |
tree | 190d3a457f4030aad1b81f35b8c665865676d86b | |
parent | ffa9030e06ad1a53141ef51493833b8019291217 (diff) | |
download | chromium_src-aab6693524a5303a9c41e5cd4f45808c82a3b01c.zip chromium_src-aab6693524a5303a9c41e5cd4f45808c82a3b01c.tar.gz chromium_src-aab6693524a5303a9c41e5cd4f45808c82a3b01c.tar.bz2 |
Experimental workaround for CHECK in NetworkChangeNotifierWin.
Also adds a related UMA histogram.
BUG=69198
TEST=NetworkChangeNotifierWinTest.*, UMA metrics
Review URL: http://codereview.chromium.org/7886036
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@104120 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | net/base/network_change_notifier.cc | 16 | ||||
-rw-r--r-- | net/base/network_change_notifier.h | 21 | ||||
-rw-r--r-- | net/base/network_change_notifier_win.cc | 83 | ||||
-rw-r--r-- | net/base/network_change_notifier_win.h | 65 | ||||
-rw-r--r-- | net/base/network_change_notifier_win_unittest.cc | 260 | ||||
-rw-r--r-- | net/net.gyp | 1 |
6 files changed, 429 insertions, 17 deletions
diff --git a/net/base/network_change_notifier.cc b/net/base/network_change_notifier.cc index 29b983e..00e6f0d 100644 --- a/net/base/network_change_notifier.cc +++ b/net/base/network_change_notifier.cc @@ -51,7 +51,10 @@ NetworkChangeNotifier* NetworkChangeNotifier::Create() { return g_network_change_notifier_factory->CreateInstance(); #if defined(OS_WIN) - return new NetworkChangeNotifierWin(); + NetworkChangeNotifierWin* network_change_notifier = + new NetworkChangeNotifierWin(); + network_change_notifier->WatchForAddressChange(); + return network_change_notifier; #elif defined(OS_CHROMEOS) // ChromeOS builds MUST use its own class factory. CHECK(false); @@ -155,4 +158,15 @@ void NetworkChangeNotifier::NotifyObserversOfOnlineStateChange() { } } +NetworkChangeNotifier::DisableForTest::DisableForTest() + : network_change_notifier_(g_network_change_notifier) { + DCHECK(g_network_change_notifier); + g_network_change_notifier = NULL; +} + +NetworkChangeNotifier::DisableForTest::~DisableForTest() { + DCHECK(!g_network_change_notifier); + g_network_change_notifier = network_change_notifier_; +} + } // namespace net diff --git a/net/base/network_change_notifier.h b/net/base/network_change_notifier.h index 39b9e05..85fff2c9 100644 --- a/net/base/network_change_notifier.h +++ b/net/base/network_change_notifier.h @@ -17,6 +17,8 @@ class NetworkChangeNotifierFactory; // NetworkChangeNotifier monitors the system for network changes, and notifies // registered observers of those events. Observers may register on any thread, // and will be called back on the thread from which they registered. +// NetworkChangeNotifiers are threadsafe, though they must be created and +// destroyed on the same thread. class NET_EXPORT NetworkChangeNotifier { public: class NET_EXPORT IPAddressObserver { @@ -134,6 +136,25 @@ class NET_EXPORT NetworkChangeNotifier { static void NotifyObserversOfDNSChange(); private: + friend class NetworkChangeNotifierWinTest; + + // Allows a second NetworkChangeNotifier to be created for unit testing, so + // the test suite can create a MockNetworkChangeNotifier, but platform + // specific NetworkChangeNotifiers can also be created for testing. To use, + // create an DisableForTest object, and then create the new + // NetworkChangeNotifier object. The NetworkChangeNotifier must be + // destroyed before the DisableForTest object, as its destruction will restore + // the original NetworkChangeNotifier. + class NET_EXPORT_PRIVATE DisableForTest { + public: + DisableForTest(); + ~DisableForTest(); + + private: + // The original NetworkChangeNotifier to be restored on destruction. + NetworkChangeNotifier* network_change_notifier_; + }; + const scoped_refptr<ObserverListThreadSafe<IPAddressObserver> > ip_address_observer_list_; const scoped_refptr<ObserverListThreadSafe<OnlineStateObserver> > diff --git a/net/base/network_change_notifier_win.cc b/net/base/network_change_notifier_win.cc index 91d6ffb..590bfca 100644 --- a/net/base/network_change_notifier_win.cc +++ b/net/base/network_change_notifier_win.cc @@ -7,23 +7,36 @@ #include <iphlpapi.h> #include <winsock2.h> +#include "base/bind.h" #include "base/logging.h" +#include "base/metrics/histogram.h" #include "base/time.h" #include "net/base/winsock_init.h" #pragma comment(lib, "iphlpapi.lib") +namespace { + +// Time between NotifyAddrChange retries, on failure. +const int kWatchForAddressChangeRetryIntervalMs = 500; + +} // namespace + namespace net { -NetworkChangeNotifierWin::NetworkChangeNotifierWin() { +NetworkChangeNotifierWin::NetworkChangeNotifierWin() + : is_watching_(false), + sequential_failures_(0), + ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) { memset(&addr_overlapped_, 0, sizeof addr_overlapped_); addr_overlapped_.hEvent = WSACreateEvent(); - WatchForAddressChange(); } NetworkChangeNotifierWin::~NetworkChangeNotifierWin() { - CancelIPChangeNotify(&addr_overlapped_); - addr_watcher_.StopWatching(); + if (is_watching_) { + CancelIPChangeNotify(&addr_overlapped_); + addr_watcher_.StopWatching(); + } WSACloseEvent(addr_overlapped_.hEvent); } @@ -145,6 +158,18 @@ bool NetworkChangeNotifierWin::IsCurrentlyOffline() const { } void NetworkChangeNotifierWin::OnObjectSignaled(HANDLE object) { + DCHECK(CalledOnValidThread()); + DCHECK(is_watching_); + is_watching_ = false; + + // Start watching for the next address change. + WatchForAddressChange(); + + NotifyObservers(); +} + +void NetworkChangeNotifierWin::NotifyObservers() { + DCHECK(CalledOnValidThread()); NotifyObserversOfIPAddressChange(); // Calling IsOffline() at this very moment is likely to give @@ -155,16 +180,58 @@ void NetworkChangeNotifierWin::OnObjectSignaled(HANDLE object) { timer_.Stop(); // cancel any already waiting notification timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(1), this, &NetworkChangeNotifierWin::NotifyParentOfOnlineStateChange); - - // Start watching for the next address change. - WatchForAddressChange(); } void NetworkChangeNotifierWin::WatchForAddressChange() { + DCHECK(CalledOnValidThread()); + DCHECK(!is_watching_); + + // NotifyAddrChange occasionally fails with ERROR_OPEN_FAILED for unknown + // reasons. More rarely, it's also been observed failing with + // ERROR_NO_SYSTEM_RESOURCES. When either of these happens, we retry later. + if (!WatchForAddressChangeInternal()) { + ++sequential_failures_; + + // TODO(mmenke): If the UMA histograms indicate that this fixes + // http://crbug.com/69198, remove this histogram and consider reducing the + // retry interval. + if (sequential_failures_ == 100) { + UMA_HISTOGRAM_COUNTS_100("Net.NotifyAddrChangeFailures", + sequential_failures_); + } + + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&NetworkChangeNotifierWin::WatchForAddressChange, + weak_factory_.GetWeakPtr()), + kWatchForAddressChangeRetryIntervalMs); + return; + } + + // Treat the transition from NotifyAddrChange failing to succeeding as a + // network change event, since network changes were not being observed in + // that interval. + if (sequential_failures_ > 0) + NotifyObservers(); + + if (sequential_failures_ < 100) { + UMA_HISTOGRAM_COUNTS_100("Net.NotifyAddrChangeFailures", + sequential_failures_); + } + + is_watching_ = true; + sequential_failures_ = 0; +} + +bool NetworkChangeNotifierWin::WatchForAddressChangeInternal() { + DCHECK(CalledOnValidThread()); HANDLE handle = NULL; DWORD ret = NotifyAddrChange(&handle, &addr_overlapped_); - CHECK_EQ(static_cast<DWORD>(ERROR_IO_PENDING), ret); + if (ret != ERROR_IO_PENDING) + return false; + addr_watcher_.StartWatching(addr_overlapped_.hEvent, this); + return true; } void NetworkChangeNotifierWin::NotifyParentOfOnlineStateChange() { diff --git a/net/base/network_change_notifier_win.h b/net/base/network_change_notifier_win.h index 9c2457e..aee5304 100644 --- a/net/base/network_change_notifier_win.h +++ b/net/base/network_change_notifier_win.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// 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. @@ -9,37 +9,86 @@ #include <windows.h> #include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/non_thread_safe.h" #include "base/timer.h" #include "base/win/object_watcher.h" +#include "net/base/net_export.h" #include "net/base/network_change_notifier.h" namespace net { -class NetworkChangeNotifierWin : public NetworkChangeNotifier, - public base::win::ObjectWatcher::Delegate { +// NetworkChangeNotifierWin inherits from NonThreadSafe, as all its internal +// notification code must be called on the thread it is created and destroyed +// on. All the NetworkChangeNotifier methods it implements are threadsafe. +class NET_EXPORT_PRIVATE NetworkChangeNotifierWin + : public NetworkChangeNotifier, + public base::win::ObjectWatcher::Delegate, + NON_EXPORTED_BASE(public base::NonThreadSafe) { public: NetworkChangeNotifierWin(); - private: + // Begins listening for a single subsequent address change. If it fails to + // start watching, it retries on a timer. Must be called only once, on the + // thread |this| was created on. This cannot be called in the constructor, as + // WatchForAddressChangeInternal is mocked out in unit tests. + // TODO(mmenke): Consider making this function a part of the + // NetworkChangeNotifier interface, so other subclasses can be + // unit tested in similar fashion, as needed. + void WatchForAddressChange(); + + protected: virtual ~NetworkChangeNotifierWin(); + // For unit tests only. + bool is_watching() { return is_watching_; } + void set_is_watching(bool is_watching) { is_watching_ = is_watching; } + int sequential_failures() { return sequential_failures_; } + + private: + friend class NetworkChangeNotifierWinTest; + // NetworkChangeNotifier methods: - virtual bool IsCurrentlyOffline() const; + virtual bool IsCurrentlyOffline() const OVERRIDE; // ObjectWatcher::Delegate methods: - virtual void OnObjectSignaled(HANDLE object); + // Must only be called on the thread |this| was created on. + virtual void OnObjectSignaled(HANDLE object) OVERRIDE; - // Begins listening for a single subsequent address change. - void WatchForAddressChange(); + // Notifies IP address change observers of a change immediately, and notifies + // network state change observers on a delay. Must only be called on the + // thread |this| was created on. + void NotifyObservers(); // Forwards online state notifications to parent class. void NotifyParentOfOnlineStateChange(); + // Tries to start listening for a single subsequent address change. Returns + // false on failure. The caller is responsible for updating |is_watching_|. + // Virtual for unit tests. Must only be called on the thread |this| was + // created on. + virtual bool WatchForAddressChangeInternal(); + + // All member variables may only be accessed on the thread |this| was created + // on. + + // False when not currently watching for network change events. This only + // happens on initialization and when WatchForAddressChangeInternal fails and + // there is a pending task to try again. Needed for safe cleanup. + bool is_watching_; + base::win::ObjectWatcher addr_watcher_; OVERLAPPED addr_overlapped_; base::OneShotTimer<NetworkChangeNotifierWin> timer_; + // Number of times WatchForAddressChange has failed in a row. + int sequential_failures_; + + // Used for calling WatchForAddressChange again on failure. + base::WeakPtrFactory<NetworkChangeNotifierWin> weak_factory_; + DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierWin); }; diff --git a/net/base/network_change_notifier_win_unittest.cc b/net/base/network_change_notifier_win_unittest.cc new file mode 100644 index 0000000..170755a --- /dev/null +++ b/net/base/network_change_notifier_win_unittest.cc @@ -0,0 +1,260 @@ +// 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 "base/message_loop.h" +#include "net/base/network_change_notifier.h" +#include "net/base/network_change_notifier_factory.h" +#include "net/base/network_change_notifier_win.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::AtLeast; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrictMock; + +namespace net { + +namespace { + +// Subclass of NetworkChangeNotifierWin that overrides functions so that no +// Windows API networking functions are ever called. +class TestNetworkChangeNotifierWin : public NetworkChangeNotifierWin { + public: + TestNetworkChangeNotifierWin() {} + + virtual ~TestNetworkChangeNotifierWin() { + // This is needed so we don't try to stop watching for IP address changes, + // as we never actually started. + set_is_watching(false); + } + + // From NetworkChangeNotifier. + virtual bool IsCurrentlyOffline() const OVERRIDE { + return false; + } + + // From NetworkChangeNotifierWin. + MOCK_METHOD0(WatchForAddressChangeInternal, bool()); + + private: + DISALLOW_COPY_AND_ASSIGN(TestNetworkChangeNotifierWin); +}; + +class TestIPAddressObserver + : public net::NetworkChangeNotifier::IPAddressObserver { + public: + TestIPAddressObserver() { + NetworkChangeNotifier::AddIPAddressObserver(this); + } + + ~TestIPAddressObserver() { + NetworkChangeNotifier::RemoveIPAddressObserver(this); + } + + MOCK_METHOD0(OnIPAddressChanged, void()); + + private: + DISALLOW_COPY_AND_ASSIGN(TestIPAddressObserver); +}; + +bool ExitMessageLoopAndReturnFalse() { + MessageLoop::current()->Quit(); + return false; +} + +} // namespace + +class NetworkChangeNotifierWinTest : public testing::Test { + public: + // Calls WatchForAddressChange, and simulates a WatchForAddressChangeInternal + // success. Expects that |network_change_notifier_| has just been created, so + // it's not watching anything yet, and there have been no previous + // WatchForAddressChangeInternal failures. + void StartWatchingAndSucceed() { + EXPECT_FALSE(network_change_notifier_.is_watching()); + EXPECT_EQ(0, network_change_notifier_.sequential_failures()); + + EXPECT_CALL(test_ip_address_observer_, OnIPAddressChanged()).Times(0); + EXPECT_CALL(network_change_notifier_, WatchForAddressChangeInternal()) + .Times(1) + .WillOnce(Return(true)); + + network_change_notifier_.WatchForAddressChange(); + + EXPECT_TRUE(network_change_notifier_.is_watching()); + EXPECT_EQ(0, network_change_notifier_.sequential_failures()); + + // If a task to notify observers of the IP address change event was + // incorrectly posted, make sure it gets run to trigger a failure. + MessageLoop::current()->RunAllPending(); + } + + // Calls WatchForAddressChange, and simulates a WatchForAddressChangeInternal + // failure. + void StartWatchingAndFail() { + EXPECT_FALSE(network_change_notifier_.is_watching()); + EXPECT_EQ(0, network_change_notifier_.sequential_failures()); + + EXPECT_CALL(test_ip_address_observer_, OnIPAddressChanged()).Times(0); + EXPECT_CALL(network_change_notifier_, WatchForAddressChangeInternal()) + // Due to an expected race, it's theoretically possible for more than + // one call to occur, though unlikely. + .Times(AtLeast(1)) + .WillRepeatedly(Return(false)); + + network_change_notifier_.WatchForAddressChange(); + + EXPECT_FALSE(network_change_notifier_.is_watching()); + EXPECT_LT(0, network_change_notifier_.sequential_failures()); + + // If a task to notify observers of the IP address change event was + // incorrectly posted, make sure it gets run. + MessageLoop::current()->RunAllPending(); + } + + // Simulates a network change event, resulting in a call to OnObjectSignaled. + // The resulting call to WatchForAddressChangeInternal then succeeds. + void SignalAndSucceed() { + EXPECT_TRUE(network_change_notifier_.is_watching()); + EXPECT_EQ(0, network_change_notifier_.sequential_failures()); + + EXPECT_CALL(test_ip_address_observer_, OnIPAddressChanged()).Times(1); + EXPECT_CALL(network_change_notifier_, WatchForAddressChangeInternal()) + .Times(1) + .WillOnce(Return(true)); + + network_change_notifier_.OnObjectSignaled(INVALID_HANDLE_VALUE); + + EXPECT_TRUE(network_change_notifier_.is_watching()); + EXPECT_EQ(0, network_change_notifier_.sequential_failures()); + + // Run the task to notify observers of the IP address change event. + MessageLoop::current()->RunAllPending(); + } + + // Simulates a network change event, resulting in a call to OnObjectSignaled. + // The resulting call to WatchForAddressChangeInternal then fails. + void SignalAndFail() { + EXPECT_TRUE(network_change_notifier_.is_watching()); + EXPECT_EQ(0, network_change_notifier_.sequential_failures()); + + EXPECT_CALL(test_ip_address_observer_, OnIPAddressChanged()).Times(1); + EXPECT_CALL(network_change_notifier_, WatchForAddressChangeInternal()) + // Due to an expected race, it's theoretically possible for more than + // one call to occur, though unlikely. + .Times(AtLeast(1)) + .WillRepeatedly(Return(false)); + + network_change_notifier_.OnObjectSignaled(INVALID_HANDLE_VALUE); + + EXPECT_FALSE(network_change_notifier_.is_watching()); + EXPECT_LT(0, network_change_notifier_.sequential_failures()); + + // Run the task to notify observers of the IP address change event. + MessageLoop::current()->RunAllPending(); + } + + // Runs the message loop until WatchForAddressChange is called again, as a + // result of the already posted task after a WatchForAddressChangeInternal + // failure. Simulates a success on the resulting call to + // WatchForAddressChangeInternal. + void RetryAndSucceed() { + EXPECT_FALSE(network_change_notifier_.is_watching()); + EXPECT_LT(0, network_change_notifier_.sequential_failures()); + + EXPECT_CALL(test_ip_address_observer_, OnIPAddressChanged()) + .Times(1) + .WillOnce(Invoke(MessageLoop::current(), &MessageLoop::Quit)); + EXPECT_CALL(network_change_notifier_, WatchForAddressChangeInternal()) + .Times(1) + .WillOnce(Return(true)); + + MessageLoop::current()->Run(); + + EXPECT_TRUE(network_change_notifier_.is_watching()); + EXPECT_EQ(0, network_change_notifier_.sequential_failures()); + } + + // Runs the message loop until WatchForAddressChange is called again, as a + // result of the already posted task after a WatchForAddressChangeInternal + // failure. Simulates a failure on the resulting call to + // WatchForAddressChangeInternal. + void RetryAndFail() { + EXPECT_FALSE(network_change_notifier_.is_watching()); + EXPECT_LT(0, network_change_notifier_.sequential_failures()); + + int initial_sequential_failures = + network_change_notifier_.sequential_failures(); + + EXPECT_CALL(test_ip_address_observer_, OnIPAddressChanged()).Times(0); + EXPECT_CALL(network_change_notifier_, WatchForAddressChangeInternal()) + // Due to an expected race, it's theoretically possible for more than + // one call to occur, though unlikely. + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(ExitMessageLoopAndReturnFalse)); + + MessageLoop::current()->Run(); + + EXPECT_FALSE(network_change_notifier_.is_watching()); + EXPECT_LT(initial_sequential_failures, + network_change_notifier_.sequential_failures()); + + // If a task to notify observers of the IP address change event was + // incorrectly posted, make sure it gets run. + MessageLoop::current()->RunAllPending(); + } + + private: + // Note that the order of declaration here is important. + + // Allows creating a new NetworkChangeNotifier. Must be created before + // |network_change_notifier_| and destroyed after it to avoid DCHECK failures. + NetworkChangeNotifier::DisableForTest disable_for_test_; + + StrictMock<TestNetworkChangeNotifierWin> network_change_notifier_; + + // Must be created after |network_change_notifier_|, so it can add itself as + // an IPAddressObserver. + StrictMock<TestIPAddressObserver> test_ip_address_observer_; +}; + +TEST_F(NetworkChangeNotifierWinTest, NetChangeWinBasic) { + StartWatchingAndSucceed(); +} + +TEST_F(NetworkChangeNotifierWinTest, NetChangeWinFailStart) { + StartWatchingAndFail(); +} + +TEST_F(NetworkChangeNotifierWinTest, NetChangeWinFailStartOnce) { + StartWatchingAndFail(); + RetryAndSucceed(); +} + +TEST_F(NetworkChangeNotifierWinTest, NetChangeWinFailStartTwice) { + StartWatchingAndFail(); + RetryAndFail(); + RetryAndSucceed(); +} + +TEST_F(NetworkChangeNotifierWinTest, NetChangeWinSignal) { + StartWatchingAndSucceed(); + SignalAndSucceed(); +} + +TEST_F(NetworkChangeNotifierWinTest, NetChangeWinFailSignalOnce) { + StartWatchingAndSucceed(); + SignalAndFail(); + RetryAndSucceed(); +} + +TEST_F(NetworkChangeNotifierWinTest, NetChangeWinFailSignalTwice) { + StartWatchingAndSucceed(); + SignalAndFail(); + RetryAndFail(); + RetryAndSucceed(); +} + +} // namespace net diff --git a/net/net.gyp b/net/net.gyp index 0bc2862..fdde802 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -949,6 +949,7 @@ 'base/net_log_unittest.cc', 'base/net_log_unittest.h', 'base/net_util_unittest.cc', + 'base/network_change_notifier_win_unittest.cc', 'base/origin_bound_cert_service_unittest.cc', 'base/pem_tokenizer_unittest.cc', 'base/registry_controlled_domain_unittest.cc', |