diff options
author | akalin@chromium.org <akalin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-30 04:23:00 +0000 |
---|---|---|
committer | akalin@chromium.org <akalin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-30 04:23:00 +0000 |
commit | f48d4e96e5de24da68e2a755e74b6cae63cd0843 (patch) | |
tree | 0b29d24d1fec62d28a4535dd586c002e9f39c186 | |
parent | 9365e0553abbefbb54cd6f82586f2a1c4790e522 (diff) | |
download | chromium_src-f48d4e96e5de24da68e2a755e74b6cae63cd0843.zip chromium_src-f48d4e96e5de24da68e2a755e74b6cae63cd0843.tar.gz chromium_src-f48d4e96e5de24da68e2a755e74b6cae63cd0843.tar.bz2 |
Added NetworkChangeObserverProxy class.
This class forwards notifications from a NetworkChangeNotifier on one
thread to an observer on another. This will be used to enable
the sync threads to listen to NCN notifications from the IO thread.
BUG=42606
TEST=unittests
Review URL: http://codereview.chromium.org/1704014
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@46037 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/sync/net/network_change_observer_proxy.cc | 131 | ||||
-rw-r--r-- | chrome/browser/sync/net/network_change_observer_proxy.h | 113 | ||||
-rw-r--r-- | chrome/browser/sync/net/network_change_observer_proxy_unittest.cc | 351 | ||||
-rw-r--r-- | chrome/chrome.gyp | 13 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 32 |
5 files changed, 640 insertions, 0 deletions
diff --git a/chrome/browser/sync/net/network_change_observer_proxy.cc b/chrome/browser/sync/net/network_change_observer_proxy.cc new file mode 100644 index 0000000..b40a530 --- /dev/null +++ b/chrome/browser/sync/net/network_change_observer_proxy.cc @@ -0,0 +1,131 @@ +// Copyright (c) 2010 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. + +// Some notes that may help anyone trying to reason about this code: +// +// - The source thread must be guaranteed to outlive the target +// thread. This is so that it is guaranteed that any task posted to +// the source thread will eventually be run. In particular, we want +// to make sure that Detach() causes us to eventually be removed as +// an observer. +// +// Note that this implies that any task posted to the target thread +// from the source thread may not run. But we post only +// TargetObserverOnIPAddressChanged() tasks on the target thread, +// which we can safely drop. +// +// - The source NetworkChangeNotifier must be guaranteed to outlive +// the target thread. This is so that it is guaranteed that any +// task posted to the source thread can safely access the source +// NetworkChangeNotifier. +// +// - Ref-counting this class is necessary, although as a consequence +// we can't make any guarantees about which thread will destroy an +// instance. Earlier versions of this class tried to get away +// without ref-counting. One version deleted the class in +// Unobserve(); this didn't work because there may still be +// TargetObserverOnIPAddressChanged() tasks on the target thread. +// An attempt to fix this was to post a DeleteTask on the target +// thread from Unobserve(), but this meant that there would be no +// way of knowing when on the target thread the instance would be +// deleted. Indeed, as mentioned above, any tasks posted on the +// target thread may not run, so this introduced the possibility of +// a memory leak. +// +// - It is important that all posted tasks that work with a proxy be +// RunnableMethods so that the ref-counting guarantees that the +// proxy is still valid when the task runs. + +#include "chrome/browser/sync/net/network_change_observer_proxy.h" + +#include <cstddef> + +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/task.h" + +namespace browser_sync { + +NetworkChangeObserverProxy::NetworkChangeObserverProxy( + MessageLoop* source_message_loop, + net::NetworkChangeNotifier* source_network_change_notifier, + MessageLoop* target_message_loop) + : source_message_loop_(source_message_loop), + source_network_change_notifier_(source_network_change_notifier), + target_message_loop_(target_message_loop), + target_observer_(NULL) { + DCHECK(source_message_loop_); + DCHECK(source_network_change_notifier_); + DCHECK(target_message_loop_); + DCHECK_NE(source_message_loop_, target_message_loop_); + DCHECK_EQ(MessageLoop::current(), target_message_loop_); +} + +NetworkChangeObserverProxy::~NetworkChangeObserverProxy() { + MessageLoop* current_message_loop = MessageLoop::current(); + // We can be deleted on either the source or target thread, so the + // best we can do is check that we're on either. + DCHECK((current_message_loop == source_message_loop_) || + (current_message_loop == target_message_loop_)); + // Even though only the target thread uses target_observer_, it + // should still be unset even if we're on the source thread; posting + // a task is effectively a memory barrier. + DCHECK(!target_observer_); +} + +void NetworkChangeObserverProxy::Observe() { + DCHECK_EQ(MessageLoop::current(), source_message_loop_); + source_network_change_notifier_->AddObserver(this); +} + +// The Task from which this was called may hold the last reference to +// us (this is how we can get deleted on the source thread). +void NetworkChangeObserverProxy::Unobserve() { + DCHECK_EQ(MessageLoop::current(), source_message_loop_); + source_network_change_notifier_->RemoveObserver(this); +} + +void NetworkChangeObserverProxy::Attach( + net::NetworkChangeNotifier::Observer* target_observer) { + DCHECK_EQ(MessageLoop::current(), target_message_loop_); + DCHECK(!target_observer_); + target_observer_ = target_observer; + DCHECK(target_observer_); + source_message_loop_->PostTask( + FROM_HERE, + NewRunnableMethod(this, &NetworkChangeObserverProxy::Observe)); +} + +void NetworkChangeObserverProxy::Detach() { + DCHECK_EQ(MessageLoop::current(), target_message_loop_); + DCHECK(target_observer_); + target_observer_ = NULL; + source_message_loop_->PostTask( + FROM_HERE, + NewRunnableMethod(this, &NetworkChangeObserverProxy::Unobserve)); +} + +// Although we may get this event after Detach() has been called on +// the target thread, we know that Unobserve() hasn't been called yet. +// But we know that it has been posted, so it at least holds a +// reference to us. +void NetworkChangeObserverProxy::OnIPAddressChanged() { + DCHECK_EQ(MessageLoop::current(), source_message_loop_); + target_message_loop_->PostTask( + FROM_HERE, + NewRunnableMethod( + this, + &NetworkChangeObserverProxy::TargetObserverOnIPAddressChanged)); +} + +// The Task from which this was called may hold the last reference to +// us (this is how we can get deleted on the target thread). +void NetworkChangeObserverProxy::TargetObserverOnIPAddressChanged() { + DCHECK_EQ(MessageLoop::current(), target_message_loop_); + if (target_observer_) { + target_observer_->OnIPAddressChanged(); + } +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/net/network_change_observer_proxy.h b/chrome/browser/sync/net/network_change_observer_proxy.h new file mode 100644 index 0000000..24eff05 --- /dev/null +++ b/chrome/browser/sync/net/network_change_observer_proxy.h @@ -0,0 +1,113 @@ +// Copyright (c) 2010 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 CHROME_BROWSER_SYNC_NET_NETWORK_CHANGE_OBSERVER_PROXY_H_ +#define CHROME_BROWSER_SYNC_NET_NETWORK_CHANGE_OBSERVER_PROXY_H_ + +// NetworkChangeObserverProxy is a class that listens to a +// NetworkChangeNotifier on one thread (the source thread, which is +// usually the Chrome IO thread) and emits events to a target observer +// on another thread (the target thread). +// +// How to use: +// +// In the target thread, create the observer proxy: +// +// NetworkChangeObserverProxy* proxy = +// new NetworkChangeObserverProxy(source_thread->message_loop(), +// source_network_change_notifier, +// MessageLoop::current()); +// +// Both source_thread and source_network_change_notifier must be +// guaranteed to outlive the target (current) thread. +// +// Then, attach the target observer: +// +// proxy->Attach(target_observer); +// +// target_observer will then begin to receive events on the target +// thread. +// +// If you call Attach(), you *must* call Detach() before releasing: +// +// proxy->Detach(); +// proxy->Release(); // omit if proxy is a scoped_refptr +// proxy = NULL; +// +// The proxy may be destroyed on either the source or the target +// thread (depending on which one ends up holding the last reference). + +#include "base/basictypes.h" +#include "base/message_loop.h" +#include "base/ref_counted.h" +#include "net/base/network_change_notifier.h" + +namespace browser_sync { + +class NetworkChangeObserverProxy + : public base::RefCountedThreadSafe<NetworkChangeObserverProxy>, + private net::NetworkChangeNotifier::Observer { + public: + // All public methods (including the constructor) must be called on + // the target thread. + + // Does not take ownership of any arguments. + NetworkChangeObserverProxy( + MessageLoop* source_message_loop, + net::NetworkChangeNotifier* source_network_change_notifier, + MessageLoop* target_message_loop); + + // After this method is called, |target_observer| will start + // receiving events on the target thread. Once called, Detach() + // must be called before releasing and you cannot call Attach() + // again until you have done so. Does not take ownership of + // |target_observer|. + void Attach(net::NetworkChangeNotifier::Observer* target_observer); + + // After this method is called, the target observer will stop + // receiving events. You can call Attach() again after this method + // is called. + void Detach(); + + protected: + // May be called on either the source or the target thread. Marked + // protected instead of private so that this class can be subclassed + // for unit tests. + virtual ~NetworkChangeObserverProxy(); + + private: + friend class base::RefCountedThreadSafe<NetworkChangeObserverProxy>; + + // Adds ourselves as an observer of + // |source_network_change_notifier_|. Must be called on the source + // thread. + void Observe(); + + // Removes ourselves as an observer of + // |source_network_change_notifier_|. Must be called on the source + // thread. + void Unobserve(); + + // net::NetworkChangeNotifier::Observer implementation. + // + // Called on the source thread. Posts + // TargetObserverOnIPAddressChanged() on the target thread. + virtual void OnIPAddressChanged(); + + // Called on the target thread. Invokes OnIPAddressChanged() on + // |target_observer_|. + void TargetObserverOnIPAddressChanged(); + + MessageLoop* const source_message_loop_; + net::NetworkChangeNotifier* const source_network_change_notifier_; + MessageLoop* const target_message_loop_; + // |target_observer_| is used only by the target thread. + net::NetworkChangeNotifier::Observer* target_observer_; + + DISALLOW_COPY_AND_ASSIGN(NetworkChangeObserverProxy); +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_NET_NETWORK_CHANGE_OBSERVER_PROXY_H_ diff --git a/chrome/browser/sync/net/network_change_observer_proxy_unittest.cc b/chrome/browser/sync/net/network_change_observer_proxy_unittest.cc new file mode 100644 index 0000000..adb0f81 --- /dev/null +++ b/chrome/browser/sync/net/network_change_observer_proxy_unittest.cc @@ -0,0 +1,351 @@ +// Copyright (c) 2010 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 "chrome/browser/sync/net/network_change_observer_proxy.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/observer_list.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "base/thread.h" +#include "base/waitable_event.h" +#include "net/base/mock_network_change_notifier.h" +#include "net/base/network_change_notifier.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace browser_sync { +class ThreadBlocker; +} // namespace browser_sync + +// We manage the lifetime of these classes ourselves. + +template <> +struct RunnableMethodTraits<browser_sync::ThreadBlocker> { + void RetainCallee(browser_sync::ThreadBlocker*) {} + void ReleaseCallee(browser_sync::ThreadBlocker*) {} +}; + +template <> +struct RunnableMethodTraits<net::MockNetworkChangeNotifier> { + void RetainCallee(net::MockNetworkChangeNotifier*) {} + void ReleaseCallee(net::MockNetworkChangeNotifier*) {} +}; + +namespace browser_sync { + +// This class lets you block and unblock a thread at will. +class ThreadBlocker { + public: + // The given thread must already be started and it must outlive this + // instance. + explicit ThreadBlocker(base::Thread* target_thread) + : target_message_loop_(target_thread->message_loop()), + is_blocked_(false, false), is_finished_blocking_(false, false), + is_unblocked_(false, false) { + DCHECK(target_message_loop_); + } + + // When this function returns, the target thread will be blocked + // until Unblock() is called. Each call to Block() must be matched + // by a call to Unblock(). + void Block() { + DCHECK_NE(MessageLoop::current(), target_message_loop_); + target_message_loop_->PostTask( + FROM_HERE, + NewRunnableMethod(this, &ThreadBlocker::BlockOnTargetThread)); + while (!is_blocked_.Wait()) {} + } + + // When this function returns, the target thread is unblocked. + void Unblock() { + DCHECK_NE(MessageLoop::current(), target_message_loop_); + is_finished_blocking_.Signal(); + while (!is_unblocked_.Wait()) {} + } + + private: + void BlockOnTargetThread() { + DCHECK_EQ(MessageLoop::current(), target_message_loop_); + is_blocked_.Signal(); + while (!is_finished_blocking_.Wait()) {} + is_unblocked_.Signal(); + } + + MessageLoop* const target_message_loop_; + base::WaitableEvent is_blocked_, is_finished_blocking_, is_unblocked_; + + DISALLOW_COPY_AND_ASSIGN(ThreadBlocker); +}; + +namespace { + +// Version of NetworkChangeObserverProxy that records on what thread +// it was deleted. +class DeleteCheckingNetworkChangeObserverProxy + : public NetworkChangeObserverProxy { + public: + // *deleting_message_loop_ must be NULL. It is set to a non-NULL + // *value when this object is deleted. + DeleteCheckingNetworkChangeObserverProxy( + MessageLoop* source_message_loop, + net::NetworkChangeNotifier* source_network_change_notifier, + MessageLoop* target_message_loop, + MessageLoop** deleting_message_loop) + : NetworkChangeObserverProxy(source_message_loop, + source_network_change_notifier, + target_message_loop), + deleting_message_loop_(deleting_message_loop) { + CHECK(deleting_message_loop_); + EXPECT_TRUE(*deleting_message_loop_ == NULL); + } + + private: + virtual ~DeleteCheckingNetworkChangeObserverProxy() { + *deleting_message_loop_ = MessageLoop::current(); + } + + MessageLoop** deleting_message_loop_; + + DISALLOW_COPY_AND_ASSIGN(DeleteCheckingNetworkChangeObserverProxy); +}; + +// Mock observer used in the unit tests. +class MockNetworkChangeObserver + : public net::NetworkChangeNotifier::Observer { + public: + MockNetworkChangeObserver() {} + + virtual ~MockNetworkChangeObserver() {} + + MOCK_METHOD0(OnIPAddressChanged, void()); + + private: + DISALLOW_COPY_AND_ASSIGN(MockNetworkChangeObserver); +}; + +// Utility function used by PumpTarget() below. +void SetTrue(bool* b) { + *b = true; +} + +class NetworkChangeObserverProxyTest : public testing::Test { + protected: + NetworkChangeObserverProxyTest() + : source_thread_("Source Thread"), + source_message_loop_(NULL), + proxy_deleting_message_loop_(NULL), + proxy_(NULL) {} + + virtual ~NetworkChangeObserverProxyTest() { + CHECK(!source_thread_blocker_.get()); + CHECK(!source_message_loop_); + } + + virtual void SetUp() { + CHECK(source_thread_.Start()); + source_message_loop_ = source_thread_.message_loop(); + source_thread_blocker_.reset(new ThreadBlocker(&source_thread_)); + source_thread_blocker_->Block(); + + proxy_deleting_message_loop_ = NULL; + proxy_ = new DeleteCheckingNetworkChangeObserverProxy( + source_message_loop_, &source_network_change_notifier_, + &target_message_loop_, &proxy_deleting_message_loop_); + } + + // On TearDown, proxy_ must be released and both source and target + // must be pumped. + virtual void TearDown() { + EXPECT_TRUE(proxy_ == NULL); + EXPECT_TRUE(proxy_deleting_message_loop_ != NULL); + source_thread_blocker_->Unblock(); + source_thread_blocker_.reset(); + source_message_loop_ = NULL; + source_thread_.Stop(); + } + + // Pump any events posted on the source thread. + void PumpSource() { + source_thread_blocker_->Unblock(); + source_thread_blocker_->Block(); + } + + // Pump any events posted on the target thread (which is just the + // main test thread). + void PumpTarget() { + bool done = false; + target_message_loop_.PostTask(FROM_HERE, + NewRunnableFunction(&SetTrue, &done)); + while (!done) { + target_message_loop_.RunAllPending(); + } + } + + // Trigger an "IP address changed" event on the source network + // change notifier on the source thread. + void OnIPAddressChanged() { + source_message_loop_->PostTask( + FROM_HERE, + NewRunnableMethod( + &source_network_change_notifier_, + &net::MockNetworkChangeNotifier::NotifyIPAddressChange)); + } + + // This lives on source_thread_ but is created/destroyed in the main + // thread. As long as we make sure to not modify it from the main + // thread while source_thread_ is running, we shouldn't need any + // special synchronization. + net::MockNetworkChangeNotifier source_network_change_notifier_; + base::Thread source_thread_; + MessageLoop* source_message_loop_; + scoped_ptr<ThreadBlocker> source_thread_blocker_; + + MessageLoop target_message_loop_; + MockNetworkChangeObserver target_observer_; + + MessageLoop* proxy_deleting_message_loop_; + scoped_refptr<NetworkChangeObserverProxy> proxy_; + + private: + DISALLOW_COPY_AND_ASSIGN(NetworkChangeObserverProxyTest); +}; + +// Make sure nothing blows up if we don't attach the proxy. +TEST_F(NetworkChangeObserverProxyTest, DoNothing) { + EXPECT_CALL(target_observer_, OnIPAddressChanged()).Times(0); + + proxy_ = NULL; + // No need to pump. + + EXPECT_EQ(proxy_deleting_message_loop_, &target_message_loop_); +} + +TEST_F(NetworkChangeObserverProxyTest, AttachDetach) { + EXPECT_CALL(target_observer_, OnIPAddressChanged()).Times(0); + + proxy_->Attach(&target_observer_); + proxy_->Detach(); + proxy_ = NULL; + PumpSource(); + + EXPECT_EQ(proxy_deleting_message_loop_, source_message_loop_); +} + +TEST_F(NetworkChangeObserverProxyTest, AttachDetachReleaseAfterPump) { + EXPECT_CALL(target_observer_, OnIPAddressChanged()).Times(0); + + proxy_->Attach(&target_observer_); + proxy_->Detach(); + PumpSource(); + proxy_ = NULL; + + EXPECT_EQ(proxy_deleting_message_loop_, &target_message_loop_); +} + +TEST_F(NetworkChangeObserverProxyTest, Basic) { + EXPECT_CALL(target_observer_, OnIPAddressChanged()).Times(1); + + proxy_->Attach(&target_observer_); + OnIPAddressChanged(); + PumpSource(); + PumpTarget(); + proxy_->Detach(); + proxy_ = NULL; + PumpSource(); + + EXPECT_EQ(proxy_deleting_message_loop_, source_message_loop_); +} + +TEST_F(NetworkChangeObserverProxyTest, Multiple) { + const int kTimes = 5; + EXPECT_CALL(target_observer_, OnIPAddressChanged()).Times(kTimes); + + proxy_->Attach(&target_observer_); + for (int i = 0; i < kTimes; ++i) { + OnIPAddressChanged(); + } + PumpSource(); + PumpTarget(); + proxy_->Detach(); + proxy_ = NULL; + PumpSource(); + + EXPECT_EQ(proxy_deleting_message_loop_, source_message_loop_); +} + +TEST_F(NetworkChangeObserverProxyTest, MultipleAttachDetach) { + const int kTimes = 5; + EXPECT_CALL(target_observer_, OnIPAddressChanged()).Times(kTimes); + + for (int i = 0; i < kTimes; ++i) { + proxy_->Attach(&target_observer_); + OnIPAddressChanged(); + PumpSource(); + PumpTarget(); + proxy_->Detach(); + } + proxy_ = NULL; + PumpSource(); + + EXPECT_EQ(proxy_deleting_message_loop_, source_message_loop_); +} + +TEST_F(NetworkChangeObserverProxyTest, IgnoresEventPostedAfterDetach) { + EXPECT_CALL(target_observer_, OnIPAddressChanged()).Times(0); + + proxy_->Attach(&target_observer_); + OnIPAddressChanged(); + proxy_->Detach(); + proxy_ = NULL; + PumpSource(); + PumpTarget(); + + EXPECT_EQ(proxy_deleting_message_loop_, &target_message_loop_); +} + +TEST_F(NetworkChangeObserverProxyTest, IgnoresEventPostedBeforeDetach) { + EXPECT_CALL(target_observer_, OnIPAddressChanged()).Times(0); + + proxy_->Attach(&target_observer_); + OnIPAddressChanged(); + PumpSource(); + proxy_->Detach(); + proxy_ = NULL; + PumpTarget(); + PumpSource(); + + EXPECT_EQ(proxy_deleting_message_loop_, source_message_loop_); +} + +TEST_F(NetworkChangeObserverProxyTest, IgnoresEventAfterDetach) { + EXPECT_CALL(target_observer_, OnIPAddressChanged()).Times(0); + + proxy_->Attach(&target_observer_); + proxy_->Detach(); + proxy_ = NULL; + OnIPAddressChanged(); + PumpSource(); + + EXPECT_EQ(proxy_deleting_message_loop_, source_message_loop_); +} + +TEST_F(NetworkChangeObserverProxyTest, IgnoresEventBeforeAttach) { + EXPECT_CALL(target_observer_, OnIPAddressChanged()).Times(0); + + OnIPAddressChanged(); + PumpSource(); + proxy_->Attach(&target_observer_); + proxy_->Detach(); + proxy_ = NULL; + PumpSource(); + + EXPECT_EQ(proxy_deleting_message_loop_, source_message_loop_); +} + +} // namespace + +} // namespace browser_sync diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 49bb392..35d7319 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -1024,6 +1024,19 @@ }], ], }, + { + # TODO(akalin): Consider moving this into its own file. + 'target_name': 'sync_net', + 'type': '<(library)', + 'sources': [ + 'browser/sync/net/network_change_observer_proxy.cc', + 'browser/sync/net/network_change_observer_proxy.h', + ], + 'dependencies': [ + '../base/base.gyp:base', + '../net/net.gyp:net_base', + ], + }, ], 'conditions': [ ['OS=="mac"', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 3b4cc66..0a9af8d 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1774,6 +1774,38 @@ ], }, { + # TODO(akalin): Add this to all.gyp. + # TODO(akalin): Consider moving this into its own file. + 'target_name': 'sync_net_unit_tests', + 'type': 'executable', + 'sources': [ + # TODO(akalin): Write our own test suite and runner. + '../base/test/run_all_unittests.cc', + '../base/test/test_suite.h', + 'browser/sync/net/network_change_observer_proxy_unittest.cc', + ], + 'include_dirs': [ + '..', + ], + 'dependencies': [ + 'sync_net', + '../testing/gmock.gyp:gmock', + '../testing/gtest.gyp:gtest', + ], + # TODO(akalin): Remove this once we have our own test suite and + # runner. + 'conditions': [ + ['OS == "linux" or OS == "freebsd" or OS == "openbsd" or OS == "solaris"', { + 'dependencies': [ + # Needed to handle the #include chain: + # base/test/test_suite.h + # gtk/gtk.h + '../build/linux/system.gyp:gtk', + ], + }], + ], + }, + { 'target_name': 'sync_integration_tests', 'type': 'executable', 'dependencies': [ |