summaryrefslogtreecommitdiffstats
path: root/chrome/browser
diff options
context:
space:
mode:
authordmaclach@chromium.org <dmaclach@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-01-14 05:36:25 +0000
committerdmaclach@chromium.org <dmaclach@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-01-14 05:36:25 +0000
commit502b937c355c991645a3b88bf59d678862b7b8ff (patch)
treee47ac89de6e31c38d32865bb936a2392d619e49f /chrome/browser
parent1b0d7c5bf0c166886b842c0044bb3192a751d212 (diff)
downloadchromium_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.cc42
-rw-r--r--chrome/browser/multi_process_notification.h109
-rw-r--r--chrome/browser/multi_process_notification_linux.cc58
-rw-r--r--chrome/browser/multi_process_notification_mac.mm545
-rw-r--r--chrome/browser/multi_process_notification_unittest.cc352
-rw-r--r--chrome/browser/multi_process_notification_win.cc58
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(), &notify_fd_, 0, &notify_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(), &notify_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