diff options
author | akalin@chromium.org <akalin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-12-15 19:20:08 +0000 |
---|---|---|
committer | akalin@chromium.org <akalin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-12-15 19:20:08 +0000 |
commit | 84c106d7a463a00b1522cda0cb8e92c021ff13c3 (patch) | |
tree | f2bf124305c8938475e596e02fbcefa3745eb1ae | |
parent | 6638395119900baf70f5c9dac2921d5a1b7aff17 (diff) | |
download | chromium_src-84c106d7a463a00b1522cda0cb8e92c021ff13c3.zip chromium_src-84c106d7a463a00b1522cda0cb8e92c021ff13c3.tar.gz chromium_src-84c106d7a463a00b1522cda0cb8e92c021ff13c3.tar.bz2 |
Implemented OS X version of NetworkStatusDetectorTaskMac.
BUG=none
TEST=trybots,manual testing
Review URL: http://codereview.chromium.org/485004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@34578 0039d316-1c4b-4281-b951-d872f2087c98
5 files changed, 516 insertions, 3 deletions
diff --git a/chrome/browser/sync/notifier/base/mac/network_status_detector_task_mac.cc b/chrome/browser/sync/notifier/base/mac/network_status_detector_task_mac.cc index 527df79..fe2faa2 100644 --- a/chrome/browser/sync/notifier/base/mac/network_status_detector_task_mac.cc +++ b/chrome/browser/sync/notifier/base/mac/network_status_detector_task_mac.cc @@ -2,14 +2,260 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/sync/notifier/base/network_status_detector_task.h" +#include "chrome/browser/sync/notifier/base/mac/network_status_detector_task_mac.h" + +#include <SystemConfiguration/SCNetworkReachability.h> + +#include "base/logging.h" +#include "base/scoped_cftyperef.h" +#include "base/scoped_ptr.h" +#include "base/string_util.h" +#include "base/sys_string_conversions.h" +#include "talk/base/physicalsocketserver.h" +#include "talk/base/socket.h" +#include "talk/base/thread.h" namespace notifier { +NetworkStatusDetectorTaskMac::WorkerInfo::WorkerInfo( + PlatformThreadId thread_id) + : thread_state(WORKER_THREAD_STOPPED), + thread_id(thread_id), + thread_run_loop(NULL) {} + +NetworkStatusDetectorTaskMac::WorkerInfo::WorkerInfo( + WorkerThreadState thread_state, + PlatformThreadId thread_id, + CFRunLoopRef thread_run_loop) + : thread_state(thread_state), + thread_id(thread_id), + thread_run_loop(thread_run_loop) { + DCHECK_EQ(thread_state == WORKER_THREAD_RUNNING, thread_run_loop != NULL); +} + +NetworkStatusDetectorTaskMac::NetworkStatusDetectorTaskMac( + talk_base::Task* parent) + : NetworkStatusDetectorTask(parent), + parent_thread_id_(PlatformThread::CurrentId()), + parent_thread_(talk_base::Thread::Current()), + worker_thread_(kNullThreadHandle), + worker_thread_not_stopped_(&worker_lock_), + worker_shared_info_(parent_thread_id_) { + DCHECK(parent_thread_); + DCHECK(IsOnParentThread()); +} + +NetworkStatusDetectorTaskMac::~NetworkStatusDetectorTaskMac() { + ClearWorker(); +} + +void NetworkStatusDetectorTaskMac::ClearWorker() { + DCHECK(IsOnParentThread()); + // Sadly, there's no Lock::AssertNotAcquired(). + WorkerThreadState worker_thread_state; + CFRunLoopRef worker_thread_run_loop; + { + AutoLock auto_lock(worker_lock_); + worker_thread_state = worker_shared_info_.thread_state; + worker_thread_run_loop = worker_shared_info_.thread_run_loop; + } + if (worker_thread_state == WORKER_THREAD_RUNNING) { + CFRunLoopStop(worker_thread_run_loop); + } + if (worker_thread_ != kNullThreadHandle) { + DCHECK_NE(worker_thread_state, WORKER_THREAD_STOPPED); + PlatformThread::Join(worker_thread_); + } + + worker_thread_ = kNullThreadHandle; + worker_shared_info_ = WorkerInfo(parent_thread_id_); +} + +bool NetworkStatusDetectorTaskMac::IsOnParentThread() const { + return PlatformThread::CurrentId() == parent_thread_id_; +} + +bool NetworkStatusDetectorTaskMac::IsOnWorkerThread() { + PlatformThreadId current_thread_id = PlatformThread::CurrentId(); + AutoLock auto_lock(worker_lock_); + return + (worker_shared_info_.thread_id != parent_thread_id_) && + (current_thread_id == worker_shared_info_.thread_id); +} + +int NetworkStatusDetectorTaskMac::ProcessStart() { + DCHECK(IsOnParentThread()); + if (logging::DEBUG_MODE) { + AutoLock auto_lock(worker_lock_); + DCHECK_EQ(worker_shared_info_.thread_state, WORKER_THREAD_STOPPED); + DCHECK(!worker_shared_info_.thread_run_loop); + DCHECK_EQ(worker_shared_info_.thread_id, parent_thread_id_); + } + + if (!PlatformThread::Create(0, this, &worker_thread_)) { + LOG(WARNING) << "Could not create network reachability thread"; + ClearWorker(); + return STATE_ERROR; + } + + // Wait for the just-created worker thread to start up and + // initialize itself. + WorkerThreadState worker_thread_state; + { + AutoLock auto_lock(worker_lock_); + while (worker_shared_info_.thread_state == WORKER_THREAD_STOPPED) { + worker_thread_not_stopped_.Wait(); + } + worker_thread_state = worker_shared_info_.thread_state; + } + + if (worker_thread_state == WORKER_THREAD_ERROR) { + ClearWorker(); + return STATE_ERROR; + } + + if (logging::DEBUG_MODE) { + AutoLock auto_lock(worker_lock_); + DCHECK_EQ(worker_shared_info_.thread_state, WORKER_THREAD_RUNNING); + DCHECK(worker_shared_info_.thread_run_loop); + DCHECK_NE(worker_shared_info_.thread_id, parent_thread_id_); + } + + return STATE_RESPONSE; +} + +void NetworkStatusDetectorTaskMac::Stop() { + ClearWorker(); + NetworkStatusDetectorTask::Stop(); +} + +void NetworkStatusDetectorTaskMac::OnMessage(talk_base::Message* message) { + DCHECK(IsOnParentThread()); + bool alive = message->message_id; + SetNetworkAlive(alive); +} + NetworkStatusDetectorTask* NetworkStatusDetectorTask::Create( talk_base::Task* parent) { - // TODO(sync): No implementation for OS X. - return NULL; + return new NetworkStatusDetectorTaskMac(parent); +} + +// Everything below is run in the worker thread. + +namespace { + +// TODO(akalin): Use these constants across all platform +// implementations. +const char kTalkHost[] = "talk.google.com"; +const int kTalkPort = 5222; + +CFStringRef NetworkReachabilityCopyDescription(const void *info) { + return base::SysUTF8ToCFStringRef( + StringPrintf("NetworkStatusDetectorTaskMac(0x%p)", info)); +} + +void NetworkReachabilityChangedCallback(SCNetworkReachabilityRef target, + SCNetworkConnectionFlags flags, + void* info) { + bool network_active = ((flags & (kSCNetworkFlagsReachable | + kSCNetworkFlagsConnectionRequired | + kSCNetworkFlagsConnectionAutomatic | + kSCNetworkFlagsInterventionRequired)) == + kSCNetworkFlagsReachable); + NetworkStatusDetectorTaskMac* network_status_detector_task_mac = + static_cast<NetworkStatusDetectorTaskMac*>(info); + network_status_detector_task_mac->NetworkReachabilityChanged( + network_active); +} + + +SCNetworkReachabilityRef CreateAndScheduleNetworkReachability( + SCNetworkReachabilityContext* network_reachability_context) { + scoped_cftyperef<SCNetworkReachabilityRef> network_reachability( + SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, kTalkHost)); + if (!network_reachability.get()) { + LOG(WARNING) << "Could not create network reachability object"; + return NULL; + } + + if (!SCNetworkReachabilitySetCallback(network_reachability.get(), + &NetworkReachabilityChangedCallback, + network_reachability_context)) { + LOG(WARNING) << "Could not set network reachability callback"; + return NULL; + } + + if (!SCNetworkReachabilityScheduleWithRunLoop(network_reachability.get(), + CFRunLoopGetCurrent(), + kCFRunLoopDefaultMode)) { + LOG(WARNING) << "Could not schedule network reachability with run loop"; + return NULL; + } + + return network_reachability.release(); +} + +} // namespace + +void NetworkStatusDetectorTaskMac::ThreadMain() { + DCHECK(!IsOnParentThread()); + PlatformThread::SetName("NetworkStatusDetectorTaskMac worker thread"); + + SCNetworkReachabilityContext network_reachability_context; + network_reachability_context.version = 0; + network_reachability_context.info = static_cast<void *>(this); + network_reachability_context.retain = NULL; + network_reachability_context.release = NULL; + network_reachability_context.copyDescription = + &NetworkReachabilityCopyDescription; + + PlatformThreadId worker_thread_id = PlatformThread::CurrentId(); + + scoped_cftyperef<SCNetworkReachabilityRef> network_reachability( + CreateAndScheduleNetworkReachability(&network_reachability_context)); + if (!network_reachability.get()) { + { + AutoLock auto_lock(worker_lock_); + worker_shared_info_ = + WorkerInfo(WORKER_THREAD_ERROR, worker_thread_id, NULL); + } + worker_thread_not_stopped_.Signal(); + return; + } + + CFRunLoopRef run_loop = CFRunLoopGetCurrent(); + { + AutoLock auto_lock(worker_lock_); + worker_shared_info_ = + WorkerInfo(WORKER_THREAD_RUNNING, worker_thread_id, run_loop); + } + worker_thread_not_stopped_.Signal(); + + DCHECK(IsOnWorkerThread()); + CFRunLoopRun(); + + // We reach here only when our run loop is stopped (usually by the + // parent thread). The parent thread is responsible for resetting + // worker_thread_shared_info_, et al. to appropriate values. +} + +void NetworkStatusDetectorTaskMac::NetworkReachabilityChanged( + bool network_active) { + DCHECK(IsOnWorkerThread()); + + bool alive = network_active; + if (alive) { + talk_base::PhysicalSocketServer physical; + scoped_ptr<talk_base::Socket> socket(physical.CreateSocket(SOCK_STREAM)); + alive = + (socket->Connect(talk_base::SocketAddress(kTalkHost, kTalkPort)) == 0); + LOG(INFO) << "network is " << (alive ? "alive" : "not alive") + << " based on connecting to " << kTalkHost << ":" << kTalkPort; + } else { + LOG(INFO) << "network is not alive"; + } + + parent_thread_->Send(this, alive); } } // namespace notifier diff --git a/chrome/browser/sync/notifier/base/mac/network_status_detector_task_mac.h b/chrome/browser/sync/notifier/base/mac/network_status_detector_task_mac.h new file mode 100644 index 0000000..bf10c60 --- /dev/null +++ b/chrome/browser/sync/notifier/base/mac/network_status_detector_task_mac.h @@ -0,0 +1,154 @@ +// Copyright (c) 2009 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_NOTIFIER_BASE_MAC_NETWORK_STATUS_DETECTOR_TASK_MAC_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_BASE_MAC_NETWORK_STATUS_DETECTOR_TASK_MAC_H_ + +#include <CoreFoundation/CoreFoundation.h> + +#include "base/basictypes.h" +#include "base/condition_variable.h" +#include "base/lock.h" +#include "base/platform_thread.h" +#include "chrome/browser/sync/notifier/base/network_status_detector_task.h" +#include "talk/base/messagequeue.h" +#include "testing/gtest/include/gtest/gtest_prod.h" + +namespace talk_base { +class Message; +class Task; +class Thread; +} // namespace talk_base + +namespace notifier { + +// The Mac OS X network status detector works as follows: a worker +// (Chrome platform) thread is spawned which simply sets up a Cocoa +// run loop and attaches the network reachability monitor to it. +// Whenever the network reachability changes, (e.g., changed wireless +// networks, unplugged ethernet cable) a callback on the worker +// thread is triggered which then tries to connect to a Google talk +// host (if the network is indicated to be up) and sends a message +// with the result to the parent thread. + +class NetworkStatusDetectorTaskMac : public NetworkStatusDetectorTask, + public PlatformThread::Delegate, + public talk_base::MessageHandler { + public: + explicit NetworkStatusDetectorTaskMac(talk_base::Task* parent); + + virtual ~NetworkStatusDetectorTaskMac(); + + // talk_base::Task functions (via NetworkStatusDetectorTask). + virtual int ProcessStart(); + virtual void Stop(); + + // talk_base::MessageHandler functions. + // Currently OnMessage() simply calls SetNetworkAlive() with alive + // set to true iff message->message_id is non-zero. + virtual void OnMessage(talk_base::Message* message); + + // Only the following public functions are called from the worker + // thread. + + // PlatformThread::Delegate functions. + virtual void ThreadMain(); + + // Called when network reachability changes. network_active should + // be set only when the network is currently active and connecting + // to a host won't require any user intervention or create a network + // connection (e.g., prompting for a password, causing a modem to + // dial). + void NetworkReachabilityChanged(bool network_active); + + private: + enum WorkerThreadState { + WORKER_THREAD_STOPPED = 1, + WORKER_THREAD_RUNNING, + WORKER_THREAD_ERROR, + }; + + // If thread_state => WORKER_THREAD_STOPPED: + // + // thread_id => parent_thread_id_ + // thread_run_loop => NULL + // possible successor states => + // { WORKER_THREAD_RUNNING, WORKER_THREAD_ERROR } + // + // If thread_state => WORKER_THREAD_RUNNING, the worker thread is + // successfully running and will continue to run until Stop() is + // called. + // + // thread_id => id of worker thread (!= parent_thread_id_) + // thread_run_loop => reference to the worker thread's run loop + // possible successor states => { WORKER_THREAD_STOPPED } + // + // If thread_state => WORKER_THREAD_ERROR, the worker thread has + // failed to start running and has stopped. Join() must be still + // called on the worker thread. + // + // thread_id => id of worker thread (!= parent_thread_id_) + // thread_run_loop => NULL + // possible successor states => { WORKER_THREAD_STOPPED } + // + // Only the worker thread can change the state from + // WORKER_THREAD_STOPPED to any other state and only the main thread + // can change the state to WORKER_THREAD_STOPPED. + struct WorkerInfo { + WorkerThreadState thread_state; + PlatformThreadId thread_id; + CFRunLoopRef thread_run_loop; + + // This constructor sets thread_state to WORKER_THREAD_STOPPED + // and thread_run_loop to NULL. + explicit WorkerInfo(PlatformThreadId thread_id); + + WorkerInfo(WorkerThreadState thread_state, + PlatformThreadId thread_id, + CFRunLoopRef thread_run_loop); + }; + + // After this function is called, worker_shared_info_.thread_state + // is guaranteed to be WORKER_THREAD_STOPPED and + // network_reachability_ is guaranteed to be NULL. Must be called + // only from the parent thread without worker_lock_ being held. + void ClearWorker(); + + bool IsOnParentThread() const; + // Acquires and releases worker_lock_. + bool IsOnWorkerThread(); + + // The thread ID of the thread that constructed this object. + PlatformThreadId parent_thread_id_; + // The libjingle thread object of the thread that constructed this + // object. + talk_base::Thread* parent_thread_; + + // The handle to the worker thread, or kNullThreadHandle if a worker + // thread doesn't exist. + PlatformThreadHandle worker_thread_; + + // This lock protects worker_shared_info_ when the worker + // thread is running. + Lock worker_lock_; + + // Signalled by the worker thread when + // worker_shared_info_.thread_state moves from WORKER_THREAD_STOPPED + // to another state. + ConditionVariable worker_thread_not_stopped_; + + // Struct for everything that is shared between the parent and the + // worker thread. + WorkerInfo worker_shared_info_; + + FRIEND_TEST(NetworkStatusDetectorTaskMacTest, StartNoStopTest); + FRIEND_TEST(NetworkStatusDetectorTaskMacTest, StartStopTest); + + DISALLOW_COPY_AND_ASSIGN(NetworkStatusDetectorTaskMac); +}; + +} // namespace notifier + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_BASE_MAC_NETWORK_STATUS_DETECTOR_TASK_MAC_H_ + diff --git a/chrome/browser/sync/notifier/base/mac/network_status_detector_task_mac_unittest.cc b/chrome/browser/sync/notifier/base/mac/network_status_detector_task_mac_unittest.cc new file mode 100644 index 0000000..351d5eb --- /dev/null +++ b/chrome/browser/sync/notifier/base/mac/network_status_detector_task_mac_unittest.cc @@ -0,0 +1,111 @@ +// Copyright (c) 2009 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/notifier/base/mac/network_status_detector_task_mac.h" + +#include <CoreFoundation/CoreFoundation.h> + +#include "talk/base/messagequeue.h" +#include "talk/base/sigslot.h" +#include "talk/base/taskrunner.h" +#include "talk/base/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace notifier { + +// No anonymous namespace because we use FRIEND_TESTs. + +class NetworkStatusDetectorTaskMacTest : public testing::Test { +}; + +// TODO(akalin): We can't test much with the current interface. +// Extend it so we're able to inject mock network events and then add +// more tests. + +// Some basic sanity checks to make sure the object is destroyed +// cleanly with various configurations. + +TEST_F(NetworkStatusDetectorTaskMacTest, InitTest) { + NetworkStatusDetectorTaskMac network_status_detector_mac(NULL); +} + +TEST_F(NetworkStatusDetectorTaskMacTest, StartNoStopTest) { + NetworkStatusDetectorTaskMac network_status_detector_mac(NULL); + EXPECT_EQ(NetworkStatusDetectorTaskMac::STATE_RESPONSE, + network_status_detector_mac.ProcessStart()); +} + +class DummyTaskRunner : public talk_base::TaskRunner { + public: + virtual void WakeTasks() {} + virtual int64 CurrentTime() { return 0; } +}; + +TEST_F(NetworkStatusDetectorTaskMacTest, StartStopTest) { + DummyTaskRunner task_runner; + NetworkStatusDetectorTaskMac network_status_detector_mac(&task_runner); + EXPECT_EQ(NetworkStatusDetectorTaskMac::STATE_RESPONSE, + network_status_detector_mac.ProcessStart()); + network_status_detector_mac.Stop(); +} + +// Some miscellaneous tests. + +class AliveListener : public sigslot::has_slots<> { + public: + AliveListener() + : was_alive_(false), + is_alive_(false), + set_alive_called_(false) {} + + void SetAlive(bool was_alive, bool is_alive) { + was_alive_ = was_alive; + is_alive_ = is_alive; + set_alive_called_ = true; + } + + void ResetSetAliveCalled() { + set_alive_called_ = false; + } + + bool was_alive() const { return was_alive_; } + bool is_alive() const { return is_alive_; } + bool set_alive_called() const { return set_alive_called_; } + + private: + bool was_alive_, is_alive_, set_alive_called_; +}; + +TEST_F(NetworkStatusDetectorTaskMacTest, OnMessageTest) { + NetworkStatusDetectorTaskMac network_status_detector_mac(NULL); + AliveListener alive_listener; + network_status_detector_mac.SignalNetworkStateDetected.connect( + &alive_listener, &AliveListener::SetAlive); + + talk_base::Message message; + + alive_listener.ResetSetAliveCalled(); + message.message_id = 0; + network_status_detector_mac.OnMessage(&message); + EXPECT_TRUE(alive_listener.set_alive_called()); + EXPECT_FALSE(alive_listener.was_alive()); + EXPECT_FALSE(alive_listener.is_alive()); + + alive_listener.ResetSetAliveCalled(); + message.message_id = 5; + network_status_detector_mac.OnMessage(&message); + EXPECT_TRUE(alive_listener.set_alive_called()); + EXPECT_FALSE(alive_listener.was_alive()); + EXPECT_TRUE(alive_listener.is_alive()); + + alive_listener.ResetSetAliveCalled(); + message.message_id = 0; + network_status_detector_mac.OnMessage(&message); + EXPECT_TRUE(alive_listener.set_alive_called()); + EXPECT_TRUE(alive_listener.was_alive()); + EXPECT_FALSE(alive_listener.is_alive()); +} + +} // namespace notifier + diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 8e909cf..031020e 100755 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -1424,6 +1424,7 @@ 'browser/sync/notifier/base/async_network_alive.h', 'browser/sync/notifier/base/fastalloc.h', 'browser/sync/notifier/base/linux/network_status_detector_task_linux.cc', + 'browser/sync/notifier/base/mac/network_status_detector_task_mac.h', 'browser/sync/notifier/base/mac/network_status_detector_task_mac.cc', 'browser/sync/notifier/base/nethelpers.cc', 'browser/sync/notifier/base/nethelpers.h', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 495ba3d..feb4fbf 100755 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1412,6 +1412,7 @@ 'browser/sync/engine/syncer_thread_unittest.cc', 'browser/sync/engine/syncer_unittest.cc', 'browser/sync/engine/syncproto_unittest.cc', + 'browser/sync/notifier/base/mac/network_status_detector_task_mac_unittest.cc', 'browser/sync/notifier/listener/talk_mediator_unittest.cc', 'browser/sync/sessions/status_controller_unittest.cc', 'browser/sync/sessions/sync_session_unittest.cc', |