diff options
author | tc@google.com <tc@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-20 20:41:00 +0000 |
---|---|---|
committer | tc@google.com <tc@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-20 20:41:00 +0000 |
commit | b674dc73f0b635aa2f7be660153e5b05f6a9b593 (patch) | |
tree | a4cd2becc248b9ea67aa42dbea9956a1148811f4 /chrome | |
parent | 7adb10a395b875ab2c8cc47c8723c70f1cfd7025 (diff) | |
download | chromium_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.h | 19 | ||||
-rw-r--r-- | chrome/browser/process_singleton_linux.cc | 277 | ||||
-rw-r--r-- | chrome/chrome.gyp | 1 |
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, ¤t_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', |