diff options
author | dmaclach@chromium.org <dmaclach@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-01-14 05:36:25 +0000 |
---|---|---|
committer | dmaclach@chromium.org <dmaclach@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-01-14 05:36:25 +0000 |
commit | 502b937c355c991645a3b88bf59d678862b7b8ff (patch) | |
tree | e47ac89de6e31c38d32865bb936a2392d619e49f /chrome/browser | |
parent | 1b0d7c5bf0c166886b842c0044bb3192a751d212 (diff) | |
download | chromium_src-502b937c355c991645a3b88bf59d678862b7b8ff.zip chromium_src-502b937c355c991645a3b88bf59d678862b7b8ff.tar.gz chromium_src-502b937c355c991645a3b88bf59d678862b7b8ff.tar.bz2 |
Add multi-process notification class.
This is a platform abstraction for a notification that can be sent between processes.
Currently only implemented on Mac. Windows and Linux will be done in a future CL.
BUG=NONE
TEST=BUILD
Committed: http://src.chromium.org/viewvc/chrome?view=rev&revision=70629
Review URL: http://codereview.chromium.org/5970015
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@71418 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
-rw-r--r-- | chrome/browser/multi_process_notification.cc | 42 | ||||
-rw-r--r-- | chrome/browser/multi_process_notification.h | 109 | ||||
-rw-r--r-- | chrome/browser/multi_process_notification_linux.cc | 58 | ||||
-rw-r--r-- | chrome/browser/multi_process_notification_mac.mm | 545 | ||||
-rw-r--r-- | chrome/browser/multi_process_notification_unittest.cc | 352 | ||||
-rw-r--r-- | chrome/browser/multi_process_notification_win.cc | 58 |
6 files changed, 1164 insertions, 0 deletions
diff --git a/chrome/browser/multi_process_notification.cc b/chrome/browser/multi_process_notification.cc new file mode 100644 index 0000000..6b7cf49 --- /dev/null +++ b/chrome/browser/multi_process_notification.cc @@ -0,0 +1,42 @@ +// 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 "chrome/browser/multi_process_notification.h" + +#include "base/task.h" + +namespace multi_process_notification { + +Listener::Delegate::~Delegate() { +} + +void Listener::Delegate::OnListenerStarted( + const std::string& name, Domain domain, bool success) { +} + +Listener::ListenerStartedTask::ListenerStartedTask(const std::string& name, + Domain domain, Listener::Delegate* delegate, bool success) + : name_(name), domain_(domain), delegate_(delegate), success_(success) { +} + +Listener::ListenerStartedTask::~ListenerStartedTask() { +} + +void Listener::ListenerStartedTask::Run() { + delegate_->OnListenerStarted(name_, domain_, success_); +} + +Listener::NotificationReceivedTask::NotificationReceivedTask( + const std::string& name, Domain domain, Listener::Delegate* delegate) + : name_(name), domain_(domain), delegate_(delegate) { +} + +Listener::NotificationReceivedTask::~NotificationReceivedTask() { +} + +void Listener::NotificationReceivedTask::Run() { + delegate_->OnNotificationReceived(name_, domain_); +} + +} // namespace multi_process_notification diff --git a/chrome/browser/multi_process_notification.h b/chrome/browser/multi_process_notification.h new file mode 100644 index 0000000..5e15801 --- /dev/null +++ b/chrome/browser/multi_process_notification.h @@ -0,0 +1,109 @@ +// 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 CHROME_BROWSER_MULTI_PROCESS_NOTIFICATION_H_ +#define CHROME_BROWSER_MULTI_PROCESS_NOTIFICATION_H_ +#pragma once + +#include <string> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/scoped_ptr.h" +#include "base/task.h" + +class Task; +class MessageLoop; + +// Platform abstraction for a notification that can be sent between processes. +// Notifications are strings. The string will be prefixed accordingly per +// platform (so on Mac OS X a "Happy" notification will become +// "org.chromium.Happy"). +namespace multi_process_notification { + +class ListenerImpl; + +enum Domain { + // Notifications intended to be received by processes running with the + // same uid and same profile. + ProfileDomain, + // Notifications intended to be received by processes running with the + // same uid. + UserDomain, + // Notifications intended to be received by processes running on the + // same system. + SystemDomain +}; + +// Posts a notification |name| to |domain|. +// Returns true if the notification was posted. +bool Post(const std::string& name, Domain domain); + +// A notification listener. Will listen for a given notification and +// call the delegate. Note that the delegate is not owned by the listener. +class Listener { + public: + class Delegate { + public: + virtual ~Delegate(); + + // OnNotificationReceived is called on the thread that called Start() on the + // Listener associated with this delegate. + virtual void OnNotificationReceived(const std::string& name, + Domain domain) = 0; + + // OnListenerStarted is called on the thread that called Start() on the + // Listener associated with this delegate. If success is false, there + // was an error starting the listener, and it is invalid. + virtual void OnListenerStarted( + const std::string& name, Domain domain, bool success); + }; + + class ListenerStartedTask : public Task { + public: + ListenerStartedTask(const std::string& name, Domain domain, + Delegate* delegate, bool success); + virtual ~ListenerStartedTask(); + virtual void Run(); + + private: + std::string name_; + Domain domain_; + Delegate* delegate_; + bool success_; + DISALLOW_COPY_AND_ASSIGN(ListenerStartedTask); + }; + + class NotificationReceivedTask : public Task { + public: + NotificationReceivedTask( + const std::string& name, Domain domain, Delegate* delegate); + virtual ~NotificationReceivedTask(); + virtual void Run(); + + private: + std::string name_; + Domain domain_; + Delegate* delegate_; + DISALLOW_COPY_AND_ASSIGN(NotificationReceivedTask); + }; + + Listener(const std::string& name, Domain domain, Delegate* delegate); + + // A destructor is required for scoped_ptr to compile. + ~Listener(); + + // A listener is not considered valid until Start() returns true and its + // delegate's OnListenerStarted method is called with |success| == true + bool Start(); + + private: + scoped_ptr<ListenerImpl> impl_; + + DISALLOW_COPY_AND_ASSIGN(Listener); +}; + +} // namespace multi_process_notification + +#endif // CHROME_BROWSER_MULTI_PROCESS_NOTIFICATION_H_ diff --git a/chrome/browser/multi_process_notification_linux.cc b/chrome/browser/multi_process_notification_linux.cc new file mode 100644 index 0000000..6d1fb12 --- /dev/null +++ b/chrome/browser/multi_process_notification_linux.cc @@ -0,0 +1,58 @@ +// 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 "chrome/browser/multi_process_notification.h" + +#include "base/logging.h" + +namespace multi_process_notification { + +bool Post(const std::string& name, Domain domain) { + // TODO(dmaclach): Implement + NOTIMPLEMENTED(); + return false; +} + +class ListenerImpl { + public: + ListenerImpl(const std::string& name, + Domain domain, + Listener::Delegate* delegate); + + bool Start(); + + private: + std::string name_; + Domain domain_; + Listener::Delegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(ListenerImpl); +}; + +ListenerImpl::ListenerImpl(const std::string& name, + Domain domain, + Listener::Delegate* delegate) + : name_(name), domain_(domain), delegate_(delegate) { +} + +bool ListenerImpl::Start() { + // TODO(dmaclach): Implement + NOTIMPLEMENTED(); + return false; +} + +Listener::Listener(const std::string& name, + Domain domain, + Listener::Delegate* delegate) + : impl_(new ListenerImpl(name, domain, delegate)) { +} + +Listener::~Listener() { +} + +bool Listener::Start() { + return impl_->Start(); +} + +} // namespace multi_process_notification diff --git a/chrome/browser/multi_process_notification_mac.mm b/chrome/browser/multi_process_notification_mac.mm new file mode 100644 index 0000000..4c76dab --- /dev/null +++ b/chrome/browser/multi_process_notification_mac.mm @@ -0,0 +1,545 @@ +// 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 "chrome/browser/multi_process_notification.h" + +#import <Foundation/Foundation.h> +#include <notify.h> +#include <sys/select.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#include <algorithm> + +#include "base/basictypes.h" +#include "base/eintr_wrapper.h" +#include "base/file_path.h" +#include "base/logging.h" +#include "base/mac/mac_util.h" +#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/message_loop_proxy.h" +#include "base/message_pump_libevent.h" +#include "base/path_service.h" +#include "base/ref_counted.h" +#include "base/stringprintf.h" +#include "base/synchronization/lock.h" +#include "base/sys_string_conversions.h" +#include "base/sys_info.h" +#include "base/threading/simple_thread.h" +#include "chrome/browser/browser_thread.h" +#include "chrome/common/chrome_paths.h" + +// Enable this to build with leopard_switchboard_thread +// #define USE_LEOPARD_SWITCHBOARD_THREAD 1 + +namespace { + +std::string AddPrefixToNotification(const std::string& name, + multi_process_notification::Domain domain) { + // The ordering of the components in the string returned by this function + // is important. Read "NAMESPACE CONVENTIONS" in 'man 3 notify' for details. + base::mac::ScopedNSAutoreleasePool pool; + NSBundle* bundle = base::mac::MainAppBundle(); + NSString* ns_bundle_id = [bundle bundleIdentifier]; + std::string bundle_id = base::SysNSStringToUTF8(ns_bundle_id); + std::string domain_string; + switch (domain) { + case multi_process_notification::ProfileDomain: { + FilePath user_data_dir; + if (!PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)) { + NOTREACHED(); + } + domain_string = StringPrintf("user.uid.%u.%s.", + getuid(), user_data_dir.value().c_str()); + break; + } + + case multi_process_notification::UserDomain: + domain_string = StringPrintf("user.uid.%u.", getuid()); + break; + + case multi_process_notification::SystemDomain: + break; + } + return domain_string + bundle_id + "." + name; +} + +bool UseLeopardSwitchboardThread() { +#if USE_LEOPARD_SWITCHBOARD_THREAD + return true; +#endif // USE_LEOPARD_SWITCHBOARD_THREAD + int32 major_version, minor_version, bugfix_version; + base::SysInfo::OperatingSystemVersionNumbers( + &major_version, &minor_version, &bugfix_version); + return major_version < 10 || (major_version == 10 && minor_version <= 5); +} + +} // namespace + +namespace multi_process_notification { + +bool Post(const std::string& name, Domain domain) { + std::string notification = AddPrefixToNotification(name, domain); + uint32_t status = notify_post(notification.c_str()); + DCHECK_EQ(status, static_cast<uint32_t>(NOTIFY_STATUS_OK)); + return status == NOTIFY_STATUS_OK; +} + +#if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_5 +#error LeopardSwitchboardThread can be removed +#endif // MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_5 + +// LeopardSwitchboardThread exists because the file descriptors returned by +// notify_register_file_descriptor can't be monitored using kqueues on 10.5 +// ( http://openradar.appspot.com/8854692 ) and libevent uses kqueue to watch +// file descriptors in IOMessageLoop. +// This solution is to have a separate thread that monitors the file descriptor +// returned by notify_register_file_descriptor using select, and then to +// notify the MessageLoopForIO using a different file descriptor allocated by +// socketpair that can be monitored using kqueues in libevent. This thread +// only runs on 10.5, as 10.6 kqueues can monitor the notify file descriptors +// without any problems. + +// LeopardSwitchboardThread creates three file descriptors: +// internal_fd_: which communicates from the switchboard thread to other threads +// external_fd_: which communicates from other threads to the switchboard thread +// notify_fd_: which is the file descriptor returned from +// notify_register_file_descriptor +// +// The thread itself sits in a select loop waiting on internal_fd_, and +// notify_fd_ for input. If it gets ANY input on internal_fd_ it exits. +// If it gets input on notify_fd_ it sends the input through to external_fd_. +// External_fd_ is monitored by MessageLoopForIO so that the lookup of any +// matching listeners in entries_, and the triggering of those listeners, +// occurs in the MessageLoopForIO thread. +// +// Lookups are linear right now, and could be optimized if they ever become +// a performance issue. +class LeopardSwitchboardThread + : public base::MessagePumpLibevent::Watcher, + public base::SimpleThread, + public MessageLoop::DestructionObserver { + public: + LeopardSwitchboardThread(); + virtual ~LeopardSwitchboardThread(); + + bool Init(); + + bool AddListener(ListenerImpl* listener, + const std::string& notification); + bool RemoveListener(ListenerImpl* listener, const std::string& notification); + + bool finished() const { return finished_; } + + // SimpleThread overrides + virtual void Run(); + + // Watcher overrides + virtual void OnFileCanReadWithoutBlocking(int fd); + virtual void OnFileCanWriteWithoutBlocking(int fd); + + // DestructionObserver overrides + virtual void WillDestroyCurrentMessageLoop(); + + private: + // Used to match tokens to notifications and vice-versa. + struct SwitchboardEntry { + int token_; + std::string notification_; + ListenerImpl* listener_; + }; + + enum { + kKillThreadMessage = 0xdecea5e + }; + + int internal_fd_; + int external_fd_; + int notify_fd_; + int notify_fd_token_; + mutable bool finished_; + fd_set fd_set_; + + // all accesses to entries_ must be controlled by entries_lock_. + std::vector<SwitchboardEntry> entries_; + Lock entries_lock_; + base::MessagePumpLibevent::FileDescriptorWatcher watcher_; +}; + +class ListenerImpl : public base::MessagePumpLibevent::Watcher { + public: + ListenerImpl(const std::string& name, + Domain domain, + Listener::Delegate* delegate); + virtual ~ListenerImpl(); + + bool Start(); + void OnListen(); + + // Watcher overrides + virtual void OnFileCanReadWithoutBlocking(int fd); + virtual void OnFileCanWriteWithoutBlocking(int fd); + + private: + std::string name_; + Domain domain_; + Listener::Delegate* delegate_; + int fd_; + int token_; + Lock switchboard_lock_; + static LeopardSwitchboardThread* g_switchboard_thread_; + base::MessagePumpLibevent::FileDescriptorWatcher watcher_; + scoped_refptr<base::MessageLoopProxy> message_loop_proxy_; + + void StartLeopard(); + void StartSnowLeopard(); + DISALLOW_COPY_AND_ASSIGN(ListenerImpl); +}; + + +LeopardSwitchboardThread::LeopardSwitchboardThread() + : base::SimpleThread("LeopardSwitchboardThread"), internal_fd_(-1), + external_fd_(-1), notify_fd_(-1), notify_fd_token_(-1), finished_(false) { +} + +LeopardSwitchboardThread::~LeopardSwitchboardThread() { + if (internal_fd_ != -1) { + close(internal_fd_); + } + if (external_fd_ != -1) { + close(external_fd_); + } + if (notify_fd_ != -1) { + // Cancelling this notification takes care of closing notify_fd_. + uint32_t status = notify_cancel(notify_fd_token_); + DCHECK_EQ(status, static_cast<uint32_t>(NOTIFY_STATUS_OK)); + } +} + +bool LeopardSwitchboardThread::Init() { + // Create a pair of sockets for communicating with the thread + // The file descriptors returned from socketpair can be kqueue'd on 10.5. + int sockets[2]; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0) { + PLOG(ERROR) << "socketpair"; + return false; + } + internal_fd_ = sockets[0]; + external_fd_ = sockets[1]; + + // Register a bogus notification so that there is single notify_fd_ to + // monitor. This runs a small risk of overflowing the notification buffer + // if notifications are used heavily (see man 3 notify), however it greatly + // simplifies the select loop code as there are only 2 file descriptors + // that need to be monitored, and there is no need to add/remove file + // descriptors from fd_set_ as listeners are added and removed. + // This also keep the total fd usage on 10.5 to three for all + // notifications. The 10.6 implementation will use one fd per notification, + // but doesn't run the risk of notification buffer overflow. If fds ever + // become tight, the 10.6 code could be changed to use only one fd for + // all notifications. + std::string notification = StringPrintf("LeopardSwitchboardThread.%d", + getpid()); + notification = AddPrefixToNotification(notification, ProfileDomain); + uint32_t status = notify_register_file_descriptor( + notification.c_str(), ¬ify_fd_, 0, ¬ify_fd_token_); + if (status != NOTIFY_STATUS_OK) { + return false; + } + + FD_ZERO(&fd_set_); + FD_SET(internal_fd_, &fd_set_); + FD_SET(notify_fd_, &fd_set_); + + MessageLoopForIO* io_loop = MessageLoopForIO::current(); + // Watch for destruction of the BrowserThread::IO message loop so that + // the thread can be stopped cleanly. + io_loop->AddDestructionObserver(this); + return io_loop->WatchFileDescriptor( + external_fd_, true, MessageLoopForIO::WATCH_READ, &watcher_, this); +} + +void LeopardSwitchboardThread::WillDestroyCurrentMessageLoop() { + DCHECK_EQ(MessageLoop::current(), MessageLoopForIO::current()); + watcher_.StopWatchingFileDescriptor(); + + // Send the appropriate message to end our thread, and then wait for it + // to finish before continuing. + int message = kKillThreadMessage; + write(external_fd_, &message, sizeof(message)); + Join(); +} + +void LeopardSwitchboardThread::Run() { + DCHECK(!finished_); + int nfds = std::max(internal_fd_, notify_fd_) + 1; + while (1) { + fd_set working_set; + FD_COPY(&fd_set_, &working_set); + int count = HANDLE_EINTR(select(nfds, &working_set, NULL, NULL, NULL)); + if (count < 0) { + PLOG(ERROR) << "select"; + break; + } else if (count == 0) { + DLOG(INFO) << "select timed out"; + continue; + } + if (FD_ISSET(notify_fd_, &working_set)) { + int token; + int status = HANDLE_EINTR(read(notify_fd_, &token, sizeof(token))); + if (status < 0) { + PLOG(ERROR) << "read"; + break; + } else if (status == 0) { + LOG(ERROR) << "notify fd closed"; + break; + } else if (status != sizeof(token)) { + LOG(ERROR) << "read wrong size: " << status; + break; + } else if (token == notify_fd_token_) { + LOG(ERROR) << "invalid token: " << token; + } + status = HANDLE_EINTR(write(internal_fd_, &token, sizeof(token))); + if (status < 0) { + PLOG(ERROR) << "write"; + break; + } else if (status == 0) { + LOG(ERROR) << "external_fd_ closed"; + break; + } else if (status != sizeof(token)) { + LOG(ERROR) << "write wrong size: " << status; + break; + } + } + if (FD_ISSET(internal_fd_, &working_set)) { + int value; + int status = HANDLE_EINTR(read(internal_fd_, &value, sizeof(value))); + if (status < 0) { + PLOG(ERROR) << "read"; + } else if (status == 0) { + LOG(ERROR) << "internal_fd_ closed"; + } else if (value != kKillThreadMessage) { + LOG(ERROR) << "unknown message sent: " << value; + } + break; + } + } + finished_ = true; +} + +bool LeopardSwitchboardThread::AddListener(ListenerImpl* listener, + const std::string& notification) { + DCHECK(!finished()); + base::AutoLock autolock(entries_lock_); + for (std::vector<SwitchboardEntry>::iterator i = entries_.begin(); + i < entries_.end(); ++i) { + if (i->listener_ == listener && i->notification_ == notification) { + LOG(ERROR) << "listener " << listener + << " already registered for '" << notification << "'"; + return false; + } + } + int token; + uint32_t status = notify_register_file_descriptor( + notification.c_str(), ¬ify_fd_, NOTIFY_REUSE, &token); + if (status != NOTIFY_STATUS_OK) { + LOG(ERROR) << "unable to notify_register_file_descriptor for '" + << notification << "' status: " << status; + return false; + } + SwitchboardEntry entry; + entry.token_ = token; + entry.notification_ = notification; + entry.listener_ = listener; + entries_.push_back(entry); + return true; +} + +bool LeopardSwitchboardThread::RemoveListener(ListenerImpl* listener, + const std::string& notification) { + DCHECK(!finished()); + base::AutoLock autolock(entries_lock_); + for (std::vector<SwitchboardEntry>::iterator i = entries_.begin(); + i < entries_.end(); ++i) { + if (i->listener_ == listener && i->notification_ == notification) { + uint32_t status = notify_cancel(i->token_); + DCHECK_EQ(status, static_cast<uint32_t>(NOTIFY_STATUS_OK)); + entries_.erase(i); + return true; + } + } + LOG(ERROR) << "unable to remove listener '" << listener + << "' for '" << notification << "'."; + return false; +} + +void LeopardSwitchboardThread::OnFileCanReadWithoutBlocking(int fd) { + DCHECK_EQ(MessageLoop::current(), MessageLoopForIO::current()); + DCHECK_EQ(fd, external_fd_); + int token; + int status = HANDLE_EINTR(read(fd, &token, sizeof(token))); + if (status < 0) { + PLOG(ERROR) << "read"; + } else if (status == 0) { + LOG(ERROR) << "external_fd_ closed"; + } else if (status != sizeof(token)) { + LOG(ERROR) << "unexpected read size " << status; + } else { + // Have to swap to native endianness <http://openradar.appspot.com/8821081>. + token = static_cast<int>(ntohl(token)); + base::AutoLock autolock(entries_lock_); + bool found_token = false; + for (std::vector<SwitchboardEntry>::iterator i = entries_.begin(); + i < entries_.end(); ++i) { + if (i->token_ == token) { + found_token = true; + i->listener_->OnListen(); + } + } + if (!found_token) { + LOG(ERROR) << "read unknown token " << token; + } + } +} + +void LeopardSwitchboardThread::OnFileCanWriteWithoutBlocking(int fd) { + NOTREACHED(); +} + +LeopardSwitchboardThread* ListenerImpl::g_switchboard_thread_ = NULL; + +ListenerImpl::ListenerImpl( + const std::string& name, Domain domain, Listener::Delegate* delegate) + : name_(name), domain_(domain), delegate_(delegate), fd_(-1), token_(-1) { +} + +ListenerImpl::~ListenerImpl() { + if (!UseLeopardSwitchboardThread()) { + if (fd_ != -1) { + uint32_t status = notify_cancel(token_); + DCHECK_EQ(status, static_cast<uint32_t>(NOTIFY_STATUS_OK)); + } + } else { + base::AutoLock autolock(switchboard_lock_); + if (g_switchboard_thread_) { + std::string notification = AddPrefixToNotification(name_, domain_); + CHECK(g_switchboard_thread_->RemoveListener(this, notification)); + } + } +} + +bool ListenerImpl::Start() { + DCHECK_EQ(fd_, -1); + DCHECK_EQ(token_, -1); + message_loop_proxy_ = base::MessageLoopProxy::CreateForCurrentThread(); + Task* task; + if(UseLeopardSwitchboardThread()) { + task = NewRunnableMethod(this, &ListenerImpl::StartLeopard); + } else { + task = NewRunnableMethod(this, &ListenerImpl::StartSnowLeopard); + } + return BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, task); +} + +void ListenerImpl::StartLeopard() { + DCHECK(UseLeopardSwitchboardThread()); + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + bool success = true; + { + base::AutoLock autolock(switchboard_lock_); + if (g_switchboard_thread_ && g_switchboard_thread_->HasBeenJoined()) { + // The only time this should ever occur is in unit tests. + delete g_switchboard_thread_; + g_switchboard_thread_ = NULL; + } + DCHECK(!(g_switchboard_thread_ && g_switchboard_thread_->finished())); + if (!g_switchboard_thread_) { + g_switchboard_thread_ = new LeopardSwitchboardThread(); + success = g_switchboard_thread_->Init(); + if (success) { + g_switchboard_thread_->Start(); + } + } + if (success) { + std::string notification = AddPrefixToNotification(name_, domain_); + success = g_switchboard_thread_->AddListener(this, notification); + } + } + Task* task = + new Listener::ListenerStartedTask(name_, domain_, delegate_, success); + CHECK(message_loop_proxy_->PostTask(FROM_HERE, task)); +} + +void ListenerImpl::StartSnowLeopard() { + DCHECK(!UseLeopardSwitchboardThread()); + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + bool success = true; + std::string notification = AddPrefixToNotification(name_, domain_); + uint32_t status = notify_register_file_descriptor( + notification.c_str(), &fd_, 0, &token_); + if (status != NOTIFY_STATUS_OK) { + LOG(ERROR) << "unable to notify_register_file_descriptor for '" + << notification << "' Status: " << status; + success = false; + } + if (success) { + MessageLoopForIO* io_loop = MessageLoopForIO::current(); + success = io_loop->WatchFileDescriptor( + fd_, true, MessageLoopForIO::WATCH_READ, &watcher_, this); + } + Task* task = + new Listener::ListenerStartedTask(name_, domain_, delegate_, success); + CHECK(message_loop_proxy_->PostTask(FROM_HERE, task)); +} + +void ListenerImpl::OnFileCanReadWithoutBlocking(int fd) { + DCHECK(!UseLeopardSwitchboardThread()); + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + DCHECK_EQ(fd, fd_); + int token; + int status = HANDLE_EINTR(read(fd, &token, sizeof(token))); + if (status < 0) { + PLOG(ERROR) << "read"; + } else if (status == 0) { + LOG(ERROR) << "external_fd_ closed"; + } else if (status != sizeof(token)) { + LOG(ERROR) << "unexpected read size " << status; + } else { + // Have to swap to native endianness <http://openradar.appspot.com/8821081>. + token = static_cast<int>(ntohl(token)); + if (token == token_) { + OnListen(); + } else { + LOG(ERROR) << "unexpected value " << token; + } + } +} + +void ListenerImpl::OnListen() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + Task* task = + new Listener::NotificationReceivedTask(name_, domain_, delegate_); + CHECK(message_loop_proxy_->PostTask(FROM_HERE, task)); +} + +void ListenerImpl::OnFileCanWriteWithoutBlocking(int fd) { + NOTREACHED(); +} + +Listener::Listener( + const std::string& name, Domain domain, Listener::Delegate* delegate) + : impl_(new ListenerImpl(name, domain, delegate)) { +} + +Listener::~Listener() { +} + +bool Listener::Start() { + return impl_->Start(); +} + +} // namespace multi_process_notification + +DISABLE_RUNNABLE_METHOD_REFCOUNT(multi_process_notification::ListenerImpl); diff --git a/chrome/browser/multi_process_notification_unittest.cc b/chrome/browser/multi_process_notification_unittest.cc new file mode 100644 index 0000000..8c42fcb --- /dev/null +++ b/chrome/browser/multi_process_notification_unittest.cc @@ -0,0 +1,352 @@ +// 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 "chrome/browser/multi_process_notification.h" + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/environment.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/test/multiprocess_test.h" +#include "base/test/test_timeouts.h" +#include "base/time.h" +#include "chrome/browser/browser_thread.h" +#include "testing/multiprocess_func_list.h" + +#if defined(OS_MACOSX) +// TODO(dmaclach): Remove defined(OS_MACOSX) once +// MultiProcessNotification is implemented on Win/Linux. + +namespace { + +const char kStartedNotificationName[] = "MultiProcessTestStartedNotification"; +const char kQuitNotificationName[] = "MultiProcessTestQuitNotification"; + +void SpinRunLoop(int milliseconds) { + MessageLoop *loop = MessageLoop::current(); + + // Post a quit task so that this loop eventually ends and we don't hang + // in the case of a bad test. Usually, the run loop will quit sooner than + // that because all tests use a MultiProcessNotificationTestQuit which quits + // the current run loop when it gets a notification. + loop->PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask(), milliseconds); + loop->Run(); +} + +class SimpleDelegate + : public multi_process_notification::Listener::Delegate { + public: + SimpleDelegate() + : notification_received_(false), started_received_(false) { } + virtual ~SimpleDelegate(); + + virtual void OnNotificationReceived( + const std::string& name, + multi_process_notification::Domain domain) OVERRIDE; + virtual void OnListenerStarted( + const std::string& name, multi_process_notification::Domain domain, + bool success) OVERRIDE; + + bool WasNotificationReceived() { return notification_received_; } + bool WasStartedReceived() { return started_received_; } + + private: + bool notification_received_; + bool started_received_; + DISALLOW_COPY_AND_ASSIGN(SimpleDelegate); +}; + +SimpleDelegate::~SimpleDelegate() { +} + +void SimpleDelegate::OnNotificationReceived( + const std::string& name, multi_process_notification::Domain domain) { + notification_received_ = true; +} + +void SimpleDelegate::OnListenerStarted( + const std::string& name, multi_process_notification::Domain domain, + bool success) { + ASSERT_TRUE(success); + started_received_ = true; + MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask()); +} + +class QuitterDelegate : public SimpleDelegate { + public: + QuitterDelegate() : SimpleDelegate() { } + virtual ~QuitterDelegate(); + + virtual void OnNotificationReceived( + const std::string& name, + multi_process_notification::Domain domain) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(QuitterDelegate); +}; + +QuitterDelegate::~QuitterDelegate() { +} + +void QuitterDelegate::OnNotificationReceived( + const std::string& name, multi_process_notification::Domain domain) { + SimpleDelegate::OnNotificationReceived(name, domain); + MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask()); +} + +int MultiProcessNotificationMain(multi_process_notification::Domain domain) { + BrowserThread browser_thread(BrowserThread::IO); + base::Thread::Options options(MessageLoop::TYPE_IO, 0); + EXPECT_TRUE(browser_thread.StartWithOptions(options)); + MessageLoop loop; + QuitterDelegate quitter; + multi_process_notification::Listener listener( + kQuitNotificationName, domain, &quitter); + EXPECT_TRUE(listener.Start()); + SpinRunLoop(TestTimeouts::action_max_timeout_ms()); + EXPECT_TRUE(quitter.WasStartedReceived()); + EXPECT_TRUE(multi_process_notification::Post(kStartedNotificationName, + domain)); + SpinRunLoop(TestTimeouts::action_max_timeout_ms()); + EXPECT_TRUE(quitter.WasNotificationReceived()); + return 0; +} + +} // namespace + +class MultiProcessNotificationTest : public base::MultiProcessTest { + public: + MultiProcessNotificationTest(); + + void PostNotificationTest(multi_process_notification::Domain domain); + void CrossPostNotificationTest(multi_process_notification::Domain domain); + + static void SetUpTestCase(); + static void TearDownTestCase(); + + private: + MessageLoop loop_; + static BrowserThread* g_io_thread; +}; + +MultiProcessNotificationTest::MultiProcessNotificationTest() { +} + +BrowserThread* MultiProcessNotificationTest::g_io_thread = NULL; + +void MultiProcessNotificationTest::SetUpTestCase() { + g_io_thread = new BrowserThread(BrowserThread::IO); + base::Thread::Options options(MessageLoop::TYPE_IO, 0); + ASSERT_TRUE(g_io_thread->StartWithOptions(options)); +} + +void MultiProcessNotificationTest::TearDownTestCase() { + delete g_io_thread; +} + +void MultiProcessNotificationTest::PostNotificationTest( + multi_process_notification::Domain domain) { + QuitterDelegate process_started; + multi_process_notification::Listener listener( + kStartedNotificationName, domain, &process_started); + ASSERT_TRUE(listener.Start()); + SpinRunLoop(TestTimeouts::action_max_timeout_ms()); + ASSERT_TRUE(process_started.WasStartedReceived()); + std::string process_name; + switch (domain) { + case multi_process_notification::ProfileDomain: + process_name = "MultiProcessProfileNotificationMain"; + break; + + case multi_process_notification::UserDomain: + process_name = "MultiProcessUserNotificationMain"; + break; + + case multi_process_notification::SystemDomain: + process_name = "MultiProcessSystemNotificationMain"; + break; + } + base::ProcessHandle handle = SpawnChild(process_name, false); + ASSERT_TRUE(handle); + SpinRunLoop(TestTimeouts::action_max_timeout_ms()); + ASSERT_TRUE(process_started.WasNotificationReceived()); + ASSERT_TRUE(multi_process_notification::Post(kQuitNotificationName, domain)); + int exit_code = 0; + EXPECT_TRUE(base::WaitForExitCodeWithTimeout( + handle, &exit_code, TestTimeouts::action_max_timeout_ms())); +} + +void MultiProcessNotificationTest::CrossPostNotificationTest( + multi_process_notification::Domain domain) { + // Check to make sure notifications sent to user domain aren't picked up + // by system domain listeners and vice versa. + std::string local_notification("QuitLocalNotification"); + std::string final_notification("FinalQuitLocalNotification"); + QuitterDelegate profile_quitter; + QuitterDelegate user_quitter; + QuitterDelegate system_quitter; + QuitterDelegate final_quitter; + multi_process_notification::Listener profile_listener( + local_notification, multi_process_notification::ProfileDomain, + &profile_quitter); + multi_process_notification::Listener user_listener( + local_notification, multi_process_notification::UserDomain, + &user_quitter); + multi_process_notification::Listener system_listener( + local_notification, multi_process_notification::SystemDomain, + &system_quitter); + multi_process_notification::Listener final_listener( + final_notification, multi_process_notification::UserDomain, + &final_quitter); + + ASSERT_TRUE(profile_listener.Start()); + SpinRunLoop(TestTimeouts::action_max_timeout_ms()); + ASSERT_TRUE(profile_quitter.WasStartedReceived()); + ASSERT_TRUE(user_listener.Start()); + SpinRunLoop(TestTimeouts::action_max_timeout_ms()); + ASSERT_TRUE(user_quitter.WasStartedReceived()); + ASSERT_TRUE(system_listener.Start()); + SpinRunLoop(TestTimeouts::action_max_timeout_ms()); + ASSERT_TRUE(system_quitter.WasStartedReceived()); + + ASSERT_TRUE(multi_process_notification::Post(local_notification, domain)); + SpinRunLoop(TestTimeouts::action_timeout_ms()); + + // Now send out a final_notification to queue up a notification + // after the local_notification and make sure that all listeners have had a + // chance to process local_notification before we check to see if they + // were called. + ASSERT_TRUE(final_listener.Start()); + SpinRunLoop(TestTimeouts::action_max_timeout_ms()); + EXPECT_TRUE(final_quitter.WasStartedReceived()); + ASSERT_TRUE(multi_process_notification::Post( + final_notification, multi_process_notification::UserDomain)); + SpinRunLoop(TestTimeouts::action_timeout_ms()); + ASSERT_TRUE(final_quitter.WasNotificationReceived()); + switch (domain) { + case multi_process_notification::ProfileDomain: + ASSERT_TRUE(profile_quitter.WasNotificationReceived()); + ASSERT_FALSE(user_quitter.WasNotificationReceived()); + ASSERT_FALSE(system_quitter.WasNotificationReceived()); + break; + + case multi_process_notification::UserDomain: + ASSERT_FALSE(profile_quitter.WasNotificationReceived()); + ASSERT_TRUE(user_quitter.WasNotificationReceived()); + ASSERT_FALSE(system_quitter.WasNotificationReceived()); + break; + + case multi_process_notification::SystemDomain: + ASSERT_FALSE(profile_quitter.WasNotificationReceived()); + ASSERT_FALSE(user_quitter.WasNotificationReceived()); + ASSERT_TRUE(system_quitter.WasNotificationReceived()); + break; + } +} + +TEST_F(MultiProcessNotificationTest, BasicCreationTest) { + QuitterDelegate quitter; + multi_process_notification::Listener local_listener( + "BasicCreationTest", multi_process_notification::UserDomain, &quitter); + ASSERT_TRUE(local_listener.Start()); + SpinRunLoop(TestTimeouts::action_max_timeout_ms()); + ASSERT_TRUE(quitter.WasStartedReceived()); + multi_process_notification::Listener system_listener( + "BasicCreationTest", multi_process_notification::SystemDomain, &quitter); + ASSERT_TRUE(system_listener.Start()); + SpinRunLoop(TestTimeouts::action_max_timeout_ms()); + ASSERT_TRUE(quitter.WasStartedReceived()); +} + +TEST_F(MultiProcessNotificationTest, PostInProcessNotification) { + std::string local_notification("QuitLocalNotification"); + QuitterDelegate quitter; + multi_process_notification::Listener listener( + local_notification, multi_process_notification::UserDomain, &quitter); + + ASSERT_TRUE(listener.Start()); + SpinRunLoop(TestTimeouts::action_max_timeout_ms()); + ASSERT_TRUE(quitter.WasStartedReceived()); + ASSERT_TRUE(multi_process_notification::Post( + local_notification, multi_process_notification::UserDomain)); + SpinRunLoop(TestTimeouts::action_max_timeout_ms()); + ASSERT_TRUE(quitter.WasNotificationReceived()); +} + +TEST_F(MultiProcessNotificationTest, MultiListener) { + std::string local_notification("LocalNotification"); + std::string quit_local_notification("QuitLocalNotification"); + + SimpleDelegate delegate1; + SimpleDelegate delegate2; + multi_process_notification::Listener local_listener1( + local_notification, multi_process_notification::UserDomain, + &delegate1); + multi_process_notification::Listener local_listener2( + local_notification, multi_process_notification::UserDomain, + &delegate2); + + QuitterDelegate quitter; + + multi_process_notification::Listener quit_listener(quit_local_notification, + multi_process_notification::UserDomain, &quitter); + + ASSERT_TRUE(local_listener1.Start()); + SpinRunLoop(TestTimeouts::action_max_timeout_ms()); + ASSERT_TRUE(delegate1.WasStartedReceived()); + ASSERT_TRUE(local_listener2.Start()); + SpinRunLoop(TestTimeouts::action_max_timeout_ms()); + ASSERT_TRUE(delegate2.WasStartedReceived()); + ASSERT_TRUE(quit_listener.Start()); + SpinRunLoop(TestTimeouts::action_max_timeout_ms()); + ASSERT_TRUE(quitter.WasStartedReceived()); + ASSERT_TRUE(multi_process_notification::Post( + local_notification, multi_process_notification::UserDomain)); + ASSERT_TRUE(multi_process_notification::Post( + quit_local_notification, multi_process_notification::UserDomain)); + SpinRunLoop(TestTimeouts::action_max_timeout_ms()); + ASSERT_TRUE(delegate1.WasNotificationReceived()); + ASSERT_TRUE(delegate2.WasNotificationReceived()); + ASSERT_TRUE(quitter.WasNotificationReceived()); +} + +TEST_F(MultiProcessNotificationTest, PostProfileNotification) { + PostNotificationTest(multi_process_notification::ProfileDomain); +} + +TEST_F(MultiProcessNotificationTest, PostUserNotification) { + PostNotificationTest(multi_process_notification::UserDomain); +} + +TEST_F(MultiProcessNotificationTest, PostSystemNotification) { + PostNotificationTest(multi_process_notification::SystemDomain); +} + +TEST_F(MultiProcessNotificationTest, ProfileCrossDomainPosting) { + CrossPostNotificationTest(multi_process_notification::ProfileDomain); +} + +TEST_F(MultiProcessNotificationTest, UserCrossDomainPosting) { + CrossPostNotificationTest(multi_process_notification::UserDomain); +} + +TEST_F(MultiProcessNotificationTest, SystemCrossDomainPosting) { + CrossPostNotificationTest(multi_process_notification::SystemDomain); +} + +MULTIPROCESS_TEST_MAIN(MultiProcessProfileNotificationMain) { + return MultiProcessNotificationMain( + multi_process_notification::ProfileDomain); +} + +MULTIPROCESS_TEST_MAIN(MultiProcessUserNotificationMain) { + return MultiProcessNotificationMain(multi_process_notification::UserDomain); +} + +MULTIPROCESS_TEST_MAIN(MultiProcessSystemNotificationMain) { + return MultiProcessNotificationMain(multi_process_notification::SystemDomain); +} + +#endif // defined(OS_MACOSX) diff --git a/chrome/browser/multi_process_notification_win.cc b/chrome/browser/multi_process_notification_win.cc new file mode 100644 index 0000000..6d1fb12 --- /dev/null +++ b/chrome/browser/multi_process_notification_win.cc @@ -0,0 +1,58 @@ +// 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 "chrome/browser/multi_process_notification.h" + +#include "base/logging.h" + +namespace multi_process_notification { + +bool Post(const std::string& name, Domain domain) { + // TODO(dmaclach): Implement + NOTIMPLEMENTED(); + return false; +} + +class ListenerImpl { + public: + ListenerImpl(const std::string& name, + Domain domain, + Listener::Delegate* delegate); + + bool Start(); + + private: + std::string name_; + Domain domain_; + Listener::Delegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(ListenerImpl); +}; + +ListenerImpl::ListenerImpl(const std::string& name, + Domain domain, + Listener::Delegate* delegate) + : name_(name), domain_(domain), delegate_(delegate) { +} + +bool ListenerImpl::Start() { + // TODO(dmaclach): Implement + NOTIMPLEMENTED(); + return false; +} + +Listener::Listener(const std::string& name, + Domain domain, + Listener::Delegate* delegate) + : impl_(new ListenerImpl(name, domain, delegate)) { +} + +Listener::~Listener() { +} + +bool Listener::Start() { + return impl_->Start(); +} + +} // namespace multi_process_notification |