summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authortc@google.com <tc@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-05-20 20:41:00 +0000
committertc@google.com <tc@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-05-20 20:41:00 +0000
commitb674dc73f0b635aa2f7be660153e5b05f6a9b593 (patch)
treea4cd2becc248b9ea67aa42dbea9956a1148811f4 /chrome
parent7adb10a395b875ab2c8cc47c8723c70f1cfd7025 (diff)
downloadchromium_src-b674dc73f0b635aa2f7be660153e5b05f6a9b593.zip
chromium_src-b674dc73f0b635aa2f7be660153e5b05f6a9b593.tar.gz
chromium_src-b674dc73f0b635aa2f7be660153e5b05f6a9b593.tar.bz2
Implement process singleton on linux so if the user tries to
open multiple chrome processes, the first one just opens a new window. This is based on http://codereview.chromium.org/88067 by Nikita Ofitserov (himikof). BUG=8073 Review URL: http://codereview.chromium.org/115572 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@16528 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r--chrome/browser/process_singleton.h19
-rw-r--r--chrome/browser/process_singleton_linux.cc277
-rw-r--r--chrome/chrome.gyp1
3 files changed, 281 insertions, 16 deletions
diff --git a/chrome/browser/process_singleton.h b/chrome/browser/process_singleton.h
index 0e39417..3ef620a 100644
--- a/chrome/browser/process_singleton.h
+++ b/chrome/browser/process_singleton.h
@@ -14,6 +14,9 @@
#include "base/basictypes.h"
#include "base/file_path.h"
#include "base/gfx/native_widget_types.h"
+#include "base/logging.h"
+#include "base/non_thread_safe.h"
+#include "base/ref_counted.h"
// ProcessSingleton ----------------------------------------------------------
//
@@ -26,7 +29,7 @@
// - the Windows implementation uses an invisible global message window;
// - the Linux implementation uses a Unix domain socket in the user data dir.
-class ProcessSingleton {
+class ProcessSingleton : public NonThreadSafe {
public:
explicit ProcessSingleton(const FilePath& user_data_dir);
~ProcessSingleton();
@@ -40,23 +43,30 @@ class ProcessSingleton {
// first one, so this function won't find it.
bool NotifyOtherProcess();
- // Set ourselves up as the singleton instance.
+ // Sets ourself up as the singleton instance.
void Create();
// Blocks the dispatch of CopyData messages. foreground_window refers
// to the window that should be set to the foreground if a CopyData message
// is received while the ProcessSingleton is locked.
void Lock(gfx::NativeWindow foreground_window) {
+ DCHECK(CalledOnValidThread());
locked_ = true;
foreground_window_ = foreground_window;
}
// Allows the dispatch of CopyData messages.
void Unlock() {
+ DCHECK(CalledOnValidThread());
locked_ = false;
foreground_window_ = NULL;
}
+ bool locked() {
+ DCHECK(CalledOnValidThread());
+ return locked_;
+ }
+
private:
bool locked_;
gfx::NativeWindow foreground_window_;
@@ -87,6 +97,11 @@ class ProcessSingleton {
// Path in file system to the socket.
FilePath socket_path_;
+
+ // Helper class for linux specific messages. LinuxWatcher is ref counted
+ // because it posts messages between threads.
+ class LinuxWatcher;
+ scoped_refptr<LinuxWatcher> watcher_;
#endif
DISALLOW_COPY_AND_ASSIGN(ProcessSingleton);
diff --git a/chrome/browser/process_singleton_linux.cc b/chrome/browser/process_singleton_linux.cc
index 57a6531..12b9370 100644
--- a/chrome/browser/process_singleton_linux.cc
+++ b/chrome/browser/process_singleton_linux.cc
@@ -2,6 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+// On Linux, when the user tries to launch a second copy of chrome, we check
+// for a socket in the user's profile directory. If the socket file is open we
+// send a message to the first chrome browser process with the current
+// directory and second process command line flags. The second process then
+// exits.
+
#include "chrome/browser/process_singleton.h"
#include <errno.h>
@@ -9,12 +15,214 @@
#include <sys/socket.h>
#include <sys/un.h>
+#include "base/base_paths.h"
+#include "base/command_line.h"
#include "base/eintr_wrapper.h"
#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/path_service.h"
#include "base/string_util.h"
+#include "chrome/browser/browser_init.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/profile_manager.h"
#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_paths.h"
+
+namespace {
+const char* kStartToken = "START";
+const char kTokenDelimiter = '\0';
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// ProcessSingleton::LinuxWatcher
+// A helper class for a Linux specific implementation of the process singleton.
+// This class sets up a listener on the singleton socket and handles parsing
+// messages that come in on the singleton socket.
+class ProcessSingleton::LinuxWatcher
+ : public MessageLoopForIO::Watcher,
+ public base::RefCountedThreadSafe<ProcessSingleton::LinuxWatcher> {
+ public:
+ class SocketReader : public MessageLoopForIO::Watcher {
+ public:
+ SocketReader(ProcessSingleton::LinuxWatcher* parent,
+ MessageLoop* ui_message_loop)
+ : parent_(parent),
+ ui_message_loop_(ui_message_loop) {}
+ virtual ~SocketReader() {}
+
+ MessageLoopForIO::FileDescriptorWatcher* fd_reader() {
+ return &fd_reader_;
+ }
+ // MessageLoopForIO::Watcher impl.
+ virtual void OnFileCanReadWithoutBlocking(int fd);
+ virtual void OnFileCanWriteWithoutBlocking(int fd) {
+ // ProcessSingleton only watches for accept (read) events.
+ NOTREACHED();
+ }
+
+ private:
+ MessageLoopForIO::FileDescriptorWatcher fd_reader_;
+
+ // The ProcessSingleton::LinuxWatcher that owns us.
+ ProcessSingleton::LinuxWatcher* parent_;
+
+ // A reference to the UI message loop.
+ MessageLoop* ui_message_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(SocketReader);
+ };
+
+ // We expect to only be constructed on the UI thread.
+ explicit LinuxWatcher(ProcessSingleton* parent)
+ : ui_message_loop_(MessageLoop::current()),
+ parent_(parent) {
+ }
+ virtual ~LinuxWatcher() {}
+
+ // This method determines if we should use the same process and if we should,
+ // opens a new browser tab. This runs on the UI thread.
+ void HandleMessage(std::string current_dir, std::vector<std::string> argv);
+
+ MessageLoopForIO::FileDescriptorWatcher* fd_watcher() {
+ return &fd_watcher_;
+ }
+ // MessageLoopForIO::Watcher impl. These run on the IO thread.
+ virtual void OnFileCanReadWithoutBlocking(int fd);
+ virtual void OnFileCanWriteWithoutBlocking(int fd) {
+ // ProcessSingleton only watches for accept (read) events.
+ NOTREACHED();
+ }
+
+ private:
+ MessageLoopForIO::FileDescriptorWatcher fd_watcher_;
+
+ // A reference to the UI message loop (i.e., the message loop we were
+ // constructed on).
+ MessageLoop* ui_message_loop_;
+
+ // The ProcessSingleton that owns us.
+ ProcessSingleton* parent_;
+
+ // The MessageLoopForIO::Watcher that actually reads data from the socket.
+ // TODO(tc): We shouldn't need to keep a pointer to this. That way we can
+ // handle multiple concurrent requests.
+ scoped_ptr<SocketReader> reader_;
+
+ DISALLOW_COPY_AND_ASSIGN(LinuxWatcher);
+};
+
+void ProcessSingleton::LinuxWatcher::OnFileCanReadWithoutBlocking(int fd) {
+ // Accepting incoming client.
+ sockaddr_un from;
+ socklen_t from_len = sizeof(from);
+ int connection_socket = HANDLE_EINTR(accept(
+ fd, reinterpret_cast<sockaddr*>(&from), &from_len));
+ if (-1 == connection_socket) {
+ LOG(ERROR) << "accept() failed: " << strerror(errno);
+ return;
+ }
+ reader_.reset(new SocketReader(this, ui_message_loop_));
+
+ // Wait for reads.
+ MessageLoopForIO::current()->WatchFileDescriptor(connection_socket,
+ true, MessageLoopForIO::WATCH_READ, reader_->fd_reader(), reader_.get());
+}
+
+void ProcessSingleton::LinuxWatcher::HandleMessage(std::string current_dir,
+ std::vector<std::string> argv) {
+ // Ignore the request if the browser process is already in shutdown path.
+ if (!g_browser_process || g_browser_process->IsShuttingDown()) {
+ LOG(WARNING) << "Not handling interprocess notification as browser"
+ " is shutting down";
+ return;
+ }
+
+ // If locked, it means we are not ready to process this message because
+ // we are probably in a first run critical phase.
+ if (parent_->locked()) {
+ DLOG(WARNING) << "Browser is locked";
+ return;
+ }
-ProcessSingleton::ProcessSingleton(const FilePath& user_data_dir) {
+ CommandLine parsed_command_line(argv);
+ PrefService* prefs = g_browser_process->local_state();
+ DCHECK(prefs);
+
+ FilePath user_data_dir;
+ PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
+ ProfileManager* profile_manager = g_browser_process->profile_manager();
+ Profile* profile = profile_manager->GetDefaultProfile(user_data_dir);
+ if (!profile) {
+ // We should only be able to get here if the profile already exists and
+ // has been created.
+ NOTREACHED();
+ return;
+ }
+
+ // TODO(tc): Send an ACK response on success.
+
+ // Run the browser startup sequence again, with the command line of the
+ // signalling process.
+ FilePath current_dir_file_path(current_dir);
+ BrowserInit::ProcessCommandLine(parsed_command_line,
+ current_dir_file_path.ToWStringHack(),
+ false, profile, NULL);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// ProcessSingleton::LinuxWatcher::SocketReader
+//
+
+void ProcessSingleton::LinuxWatcher::SocketReader::OnFileCanReadWithoutBlocking(
+ int fd) {
+ const int kMaxMessageLength = 32 * 1024;
+ char buf[kMaxMessageLength];
+ ssize_t rv = HANDLE_EINTR(read(fd, buf, sizeof(buf)));
+ if (rv < 0) {
+ LOG(ERROR) << "recv() failed: " << strerror(errno);
+ return;
+ }
+
+ // Validate the message. The shortest message is kStartToken\0x\0x\0
+ const ssize_t kMinMessageLength = strlen(kStartToken) + 5;
+ if (rv < kMinMessageLength) {
+ LOG(ERROR) << "Invalid socket message (wrong length):" << buf;
+ return;
+ }
+
+ std::string str(buf, rv);
+ std::vector<std::string> tokens;
+ SplitString(str, kTokenDelimiter, &tokens);
+
+ if (tokens.size() < 3 || tokens[0] != kStartToken) {
+ LOG(ERROR) << "Wrong message format: " << str;
+ return;
+ }
+
+ std::string current_dir = tokens[1];
+ // Remove the first two tokens. The remaining tokens should be the command
+ // line argv array.
+ tokens.erase(tokens.begin());
+ tokens.erase(tokens.begin());
+
+ // Return to the UI thread to handle opening a new browser tab.
+ ui_message_loop_->PostTask(FROM_HERE, NewRunnableMethod(
+ parent_,
+ &ProcessSingleton::LinuxWatcher::HandleMessage,
+ current_dir,
+ tokens));
+ fd_reader_.StopWatchingFileDescriptor();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// ProcessSingleton
+//
+ProcessSingleton::ProcessSingleton(const FilePath& user_data_dir)
+ : locked_(false),
+ foreground_window_(NULL),
+ ALLOW_THIS_IN_INITIALIZER_LIST(watcher_(new LinuxWatcher(this))) {
socket_path_ = user_data_dir.Append(chrome::kSingletonSocketFilename);
}
@@ -22,21 +230,58 @@ ProcessSingleton::~ProcessSingleton() {
}
bool ProcessSingleton::NotifyOtherProcess() {
- int sock;
+ int socket;
sockaddr_un addr;
- SetupSocket(&sock, &addr);
+ SetupSocket(&socket, &addr);
- if (HANDLE_EINTR(connect(sock, reinterpret_cast<sockaddr*>(&addr),
- sizeof(addr))) < 0 &&
- (errno == ENOENT || errno == ECONNREFUSED)) {
+ // Connecting to the socket
+ int ret = HANDLE_EINTR(connect(socket,
+ reinterpret_cast<sockaddr*>(&addr),
+ sizeof(addr)));
+ if (ret < 0)
return false; // Tell the caller there's nobody to notify.
+
+ timeval timeout = {20, 0};
+ setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
+
+ // Found another process, prepare our command line
+ // format is "START\0<current dir>\0<argv[0]>\0...\0<argv[n]>\0".
+ std::string to_send(kStartToken);
+ to_send.push_back(kTokenDelimiter);
+
+ FilePath current_dir;
+ if (!PathService::Get(base::DIR_CURRENT, &current_dir))
+ return false;
+ to_send.append(current_dir.value());
+ to_send.push_back(kTokenDelimiter);
+
+ const std::vector<std::string>& argv =
+ CommandLine::ForCurrentProcess()->argv();
+ for (std::vector<std::string>::const_iterator it = argv.begin();
+ it != argv.end(); ++it) {
+ to_send.append(*it);
+ to_send.push_back(kTokenDelimiter);
+ }
+
+ // Send the message
+ int rv = HANDLE_EINTR(write(socket, to_send.data(), to_send.length()));
+ if (rv < 0) {
+ if (errno == EAGAIN) {
+ // TODO(tc): We should kill the hung process here.
+ NOTIMPLEMENTED() << "browser process hung, don't know how to kill it";
+ return false;
+ }
+ LOG(ERROR) << "send() failed: " << strerror(errno);
+ return false;
}
- // TODO(port): pass in info to the other process.
- // http://code.google.com/p/chromium/issues/detail?id=8073
- NOTIMPLEMENTED() << " don't know how to notify other process about us.";
+ // TODO(tc): We should wait for an ACK, and if we don't get it in a certain
+ // time period, kill the other process.
+
+ HANDLE_EINTR(close(socket));
- return true; // We did our best, so we die here.
+ // Assume the other process is handling the request.
+ return true;
}
void ProcessSingleton::Create() {
@@ -53,9 +298,15 @@ void ProcessSingleton::Create() {
if (listen(sock, 5) < 0)
NOTREACHED() << "listen failed: " << strerror(errno);
- // TODO(port): register this socket as something we care about getting
- // input on, process messages, etc.
- // http://code.google.com/p/chromium/issues/detail?id=8073
+ // Normally we would use ChromeThread, but the IO thread hasn't started yet.
+ // Using g_browser_process, we start the thread so we can listen on the
+ // socket.
+ MessageLoop* ml = g_browser_process->io_thread()->message_loop();
+ DCHECK(ml);
+
+ // Watch for client connections on this socket.
+ static_cast<MessageLoopForIO*>(ml)->WatchFileDescriptor(sock, true,
+ MessageLoopForIO::WATCH_READ, watcher_->fd_watcher(), watcher_.get());
}
void ProcessSingleton::SetupSocket(int* sock, struct sockaddr_un* addr) {
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp
index f60d671..9e73883 100644
--- a/chrome/chrome.gyp
+++ b/chrome/chrome.gyp
@@ -2579,7 +2579,6 @@
],
'sources!': [
# TODO(port)
- 'app/chrome_main_uitest.cc',
'browser/crash_recovery_uitest.cc',
'browser/login_prompt_uitest.cc',
'browser/metrics/metrics_service_uitest.cc',