diff options
-rw-r--r-- | chrome/chrome_common.gypi | 4 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 1 | ||||
-rw-r--r-- | chrome/common/multi_process_lock.h | 39 | ||||
-rw-r--r-- | chrome/common/multi_process_lock_linux.cc | 95 | ||||
-rw-r--r-- | chrome/common/multi_process_lock_mac.cc | 54 | ||||
-rw-r--r-- | chrome/common/multi_process_lock_unittest.cc | 157 | ||||
-rw-r--r-- | chrome/common/multi_process_lock_win.cc | 58 |
7 files changed, 408 insertions, 0 deletions
diff --git a/chrome/chrome_common.gypi b/chrome/chrome_common.gypi index 89aeadc..f7f44fc 100644 --- a/chrome/chrome_common.gypi +++ b/chrome/chrome_common.gypi @@ -86,6 +86,10 @@ 'common/message_router.h', 'common/metrics_helpers.cc', 'common/metrics_helpers.h', + 'common/multi_process_lock.h', + 'common/multi_process_lock_linux.cc', + 'common/multi_process_lock_mac.cc', + 'common/multi_process_lock_win.cc', 'common/nacl_cmd_line.cc', 'common/nacl_cmd_line.h', 'common/nacl_messages.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 6ea2d6c..354e1ca 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1570,6 +1570,7 @@ 'common/json_schema_validator_unittest.cc', 'common/json_value_serializer_unittest.cc', 'common/mru_cache_unittest.cc', + 'common/multi_process_lock_unittest.cc', 'common/net/gaia/gaia_auth_fetcher_unittest.cc', 'common/net/gaia/gaia_auth_fetcher_unittest.h', 'common/net/gaia/gaia_authenticator_unittest.cc', diff --git a/chrome/common/multi_process_lock.h b/chrome/common/multi_process_lock.h new file mode 100644 index 0000000..24754f5 --- /dev/null +++ b/chrome/common/multi_process_lock.h @@ -0,0 +1,39 @@ +// Copyright (c) 2010 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_COMMON_MULTI_PROCESS_LOCK_H_ +#define CHROME_COMMON_MULTI_PROCESS_LOCK_H_ +#pragma once + +#include <sys/types.h> +#include <string> + +// Platform abstraction for a lock that can be shared between processes. +// The process that owns the lock will release it on exit even if +// the exit is due to a crash. Locks are not recursive. +class MultiProcessLock { + public: + + // The length of a multi-process lock name is limited on Linux, so + // it is limited it on all platforms for consistency. This length does + // not include a terminator. + static const size_t MULTI_PROCESS_LOCK_NAME_MAX_LEN = 106; + + // Factory method for creating a multi-process lock. + // |name| is the name of the lock. The name has special meaning on Windows + // where the prefix can determine the namespace of the lock. + // See http://msdn.microsoft.com/en-us/library/aa382954(v=VS.85).aspx for + // details. + static MultiProcessLock* Create(const std::string& name); + + virtual ~MultiProcessLock() { } + + // Try to grab ownership of the lock. + virtual bool TryLock() = 0; + + // Release ownership of the lock. + virtual void Unlock() = 0; +}; + +#endif // CHROME_COMMON_MULTI_PROCESS_LOCK_H_ diff --git a/chrome/common/multi_process_lock_linux.cc b/chrome/common/multi_process_lock_linux.cc new file mode 100644 index 0000000..0a83c89 --- /dev/null +++ b/chrome/common/multi_process_lock_linux.cc @@ -0,0 +1,95 @@ +// Copyright (c) 2010 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/common/multi_process_lock.h" + +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> + +#include "base/eintr_wrapper.h" +#include "base/logging.h" + +class MultiProcessLockLinux : public MultiProcessLock { + public: + explicit MultiProcessLockLinux(const std::string& name) + : name_(name), fd_(-1) { } + + virtual ~MultiProcessLockLinux() { + if (fd_ != -1) { + Unlock(); + } + } + + virtual bool TryLock() { + if (fd_ != -1) { + DLOG(ERROR) << "MultiProcessLock is already locked - " << name_; + return true; + } + + if (name_.length() > MULTI_PROCESS_LOCK_NAME_MAX_LEN) { + LOG(ERROR) << "Socket name too long - " << name_; + return false; + } + + struct sockaddr_un address; + + // +1 for terminator, +1 for 0 in position 0 that makes it an + // abstract named socket. + // If this assert fails it is because sockaddr_un.sun_path size has been + // redefined and MULTI_PROCESS_LOCK_NAME_MAX_LEN can change accordingly. + COMPILE_ASSERT(sizeof(address.sun_path) + == MULTI_PROCESS_LOCK_NAME_MAX_LEN + 2, sun_path_size_changed); + + memset(&address, 0, sizeof(address)); + strcpy(&address.sun_path[1], name_.c_str()); + + // Must set the first character of the path to something non-zero + // before we call SUN_LEN which depends on strcpy working. + address.sun_path[0] = '@'; + size_t length = SUN_LEN(&address); + + // Reset the first character of the path back to zero so that + // bind returns an abstract name socket. + address.sun_path[0] = 0; + address.sun_family = AF_LOCAL; + + int socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (socket_fd < 0) { + PLOG(ERROR) << "Couldn't create socket - " << name_; + return false; + } + + if (bind(socket_fd, + reinterpret_cast<sockaddr *>(&address), + length) == 0) { + fd_ = socket_fd; + return true; + } else { + PLOG(ERROR) << "Couldn't bind socket - " + << &(address.sun_path[1]) + << " Length: " << length; + HANDLE_EINTR(close(socket_fd)); + return false; + } + } + + virtual void Unlock() { + if (fd_ == -1) { + DLOG(ERROR) << "Over-unlocked MultiProcessLock - " << name_; + return; + } + HANDLE_EINTR(close(fd_)); + fd_ = -1; + } + + private: + std::string name_; + int fd_; + DISALLOW_COPY_AND_ASSIGN(MultiProcessLockLinux); +}; + +MultiProcessLock* MultiProcessLock::Create(const std::string &name) { + return new MultiProcessLockLinux(name); +} diff --git a/chrome/common/multi_process_lock_mac.cc b/chrome/common/multi_process_lock_mac.cc new file mode 100644 index 0000000..9a16d71 --- /dev/null +++ b/chrome/common/multi_process_lock_mac.cc @@ -0,0 +1,54 @@ +// Copyright (c) 2010 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/common/multi_process_lock.h" + +#include "base/logging.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/sys_string_conversions.h" + +class MultiProcessLockMac : public MultiProcessLock { + public: + explicit MultiProcessLockMac(const std::string& name) : name_(name) { } + + virtual ~MultiProcessLockMac() { + if (port_ != NULL) { + Unlock(); + } + } + + virtual bool TryLock() { + if (port_ != NULL) { + DLOG(ERROR) << "MultiProcessLock is already locked - " << name_; + return true; + } + + if (name_.length() > MULTI_PROCESS_LOCK_NAME_MAX_LEN) { + LOG(ERROR) << "Socket name too long - " << name_; + return false; + } + + CFStringRef cf_name(base::SysUTF8ToCFStringRef(name_)); + base::mac::ScopedCFTypeRef<CFStringRef> scoped_cf_name(cf_name); + port_.reset(CFMessagePortCreateLocal(NULL, cf_name, NULL, NULL, NULL)); + return port_ != NULL; + } + + virtual void Unlock() { + if (port_ == NULL) { + DLOG(ERROR) << "Over-unlocked MultiProcessLock - " << name_; + return; + } + port_.reset(); + } + + private: + std::string name_; + base::mac::ScopedCFTypeRef<CFMessagePortRef> port_; + DISALLOW_COPY_AND_ASSIGN(MultiProcessLockMac); +}; + +MultiProcessLock* MultiProcessLock::Create(const std::string &name) { + return new MultiProcessLockMac(name); +} diff --git a/chrome/common/multi_process_lock_unittest.cc b/chrome/common/multi_process_lock_unittest.cc new file mode 100644 index 0000000..733eb2d --- /dev/null +++ b/chrome/common/multi_process_lock_unittest.cc @@ -0,0 +1,157 @@ +// Copyright (c) 2010 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 "base/basictypes.h" +#include "base/environment.h" +#include "base/logging.h" +#include "base/rand_util.h" +#include "base/scoped_ptr.h" +#include "base/stringprintf.h" +#include "base/test/multiprocess_test.h" +#include "base/time.h" +#include "chrome/common/multi_process_lock.h" +#include "testing/multiprocess_func_list.h" + +class MultiProcessLockTest : public base::MultiProcessTest { + public: + static const char kLockEnviromentVarName[]; + + class ScopedEnvironmentVariable { + public: + ScopedEnvironmentVariable(const std::string &name, + const std::string &value) + : name_(name), environment_(base::Environment::Create()) { + environment_->SetVar(name_.c_str(), value); + } + ~ScopedEnvironmentVariable() { + environment_->UnSetVar(name_.c_str()); + } + + private: + std::string name_; + scoped_ptr<base::Environment> environment_; + DISALLOW_COPY_AND_ASSIGN(ScopedEnvironmentVariable); + }; + + std::string GenerateLockName(); + void ExpectLockIsLocked(const std::string &name); + void ExpectLockIsUnlocked(const std::string &name); +}; + +const char MultiProcessLockTest::kLockEnviromentVarName[] + = "MULTI_PROCESS_TEST_LOCK_NAME"; + +std::string MultiProcessLockTest::GenerateLockName() { + base::Time now = base::Time::NowFromSystemTime(); + return base::StringPrintf("multi_process_test_lock %lf%lf", + now.ToDoubleT(), base::RandDouble()); +} + +void MultiProcessLockTest::ExpectLockIsLocked(const std::string &name) { + ScopedEnvironmentVariable var(kLockEnviromentVarName, name); + base::ProcessHandle handle = SpawnChild("MultiProcessLockTryFailMain", false); + ASSERT_TRUE(handle); + int exit_code = 0; + EXPECT_TRUE(base::WaitForExitCode(handle, &exit_code)); + EXPECT_EQ(exit_code, 0); +} + +void MultiProcessLockTest::ExpectLockIsUnlocked( + const std::string &name) { + ScopedEnvironmentVariable var(kLockEnviromentVarName, name); + base::ProcessHandle handle = SpawnChild("MultiProcessLockTrySucceedMain", + false); + ASSERT_TRUE(handle); + int exit_code = 0; + EXPECT_TRUE(base::WaitForExitCode(handle, &exit_code)); + EXPECT_EQ(exit_code, 0); +} + +TEST_F(MultiProcessLockTest, BasicCreationTest) { + // Test basic creation/destruction with no lock taken + std::string name = GenerateLockName(); + scoped_ptr<MultiProcessLock> scoped(MultiProcessLock::Create(name)); + ExpectLockIsUnlocked(name); + scoped.reset(NULL); +} + +TEST_F(MultiProcessLockTest, LongNameTest) { + // Linux has a max path name of 108 characters. + // http://lxr.linux.no/linux+v2.6.36/include/linux/un.h + // This is enforced on all platforms. + LOG(INFO) << "Following error log due to long name is expected"; + std::string name("This is a name that is longer than one hundred and eight " + "characters to make sure that we fail appropriately on linux when we " + "have a path that is to long for linux to handle"); + scoped_ptr<MultiProcessLock> test_lock(MultiProcessLock::Create(name)); + EXPECT_FALSE(test_lock->TryLock()); +} + +TEST_F(MultiProcessLockTest, SimpleLock) { + std::string name = GenerateLockName(); + scoped_ptr<MultiProcessLock> test_lock(MultiProcessLock::Create(name)); + EXPECT_TRUE(test_lock->TryLock()); + ExpectLockIsLocked(name); + test_lock->Unlock(); + ExpectLockIsUnlocked(name); +} + +TEST_F(MultiProcessLockTest, RecursiveLock) { + std::string name = GenerateLockName(); + scoped_ptr<MultiProcessLock> test_lock(MultiProcessLock::Create(name)); + EXPECT_TRUE(test_lock->TryLock()); + ExpectLockIsLocked(name); + LOG(INFO) << "Following error log " + << "'MultiProcessLock is already locked' is expected"; + EXPECT_TRUE(test_lock->TryLock()); + ExpectLockIsLocked(name); + test_lock->Unlock(); + ExpectLockIsUnlocked(name); + LOG(INFO) << "Following error log " + << "'Over-unlocked MultiProcessLock' is expected"; + test_lock->Unlock(); + ExpectLockIsUnlocked(name); + test_lock.reset(); +} + +TEST_F(MultiProcessLockTest, LockScope) { + // Check to see that lock is released when it goes out of scope. + std::string name = GenerateLockName(); + { + scoped_ptr<MultiProcessLock> test_lock(MultiProcessLock::Create(name)); + EXPECT_TRUE(test_lock->TryLock()); + ExpectLockIsLocked(name); + } + ExpectLockIsUnlocked(name); +} + +MULTIPROCESS_TEST_MAIN(MultiProcessLockTryFailMain) { + std::string name; + scoped_ptr<base::Environment> environment(base::Environment::Create()); + EXPECT_TRUE(environment->GetVar(MultiProcessLockTest::kLockEnviromentVarName, + &name)); +#if defined(OS_MACOSX) + // OS X sends out a log if a lock fails. + // Hopefully this will keep people from panicking about it when they + // are perusing thge build logs. + LOG(INFO) << "Following error log " + << "\"CFMessagePort: bootstrap_register(): failed 1100 (0x44c) " + << "'Permission denied'\" is expected"; +#endif // defined(OS_MACOSX) + scoped_ptr<MultiProcessLock> test_lock( + MultiProcessLock::Create(name)); + EXPECT_FALSE(test_lock->TryLock()); + return 0; +} + +MULTIPROCESS_TEST_MAIN(MultiProcessLockTrySucceedMain) { + std::string name; + scoped_ptr<base::Environment> environment(base::Environment::Create()); + EXPECT_TRUE(environment->GetVar(MultiProcessLockTest::kLockEnviromentVarName, + &name)); + scoped_ptr<MultiProcessLock> test_lock( + MultiProcessLock::Create(name)); + EXPECT_TRUE(test_lock->TryLock()); + return 0; +} diff --git a/chrome/common/multi_process_lock_win.cc b/chrome/common/multi_process_lock_win.cc new file mode 100644 index 0000000..8bb525d --- /dev/null +++ b/chrome/common/multi_process_lock_win.cc @@ -0,0 +1,58 @@ +// Copyright (c) 2010 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/common/multi_process_lock.h" + +#include "base/logging.h" +#include "base/utf_string_conversions.h" +#include "base/win/scoped_handle.h" + +class MultiProcessLockWin : public MultiProcessLock { + public: + explicit MultiProcessLockWin(const std::string& name) : name_(name) { } + + virtual ~MultiProcessLockWin() { + if (event_.Get() != NULL) { + Unlock(); + } + } + + virtual bool TryLock() { + if (event_.Get() != NULL) { + DLOG(ERROR) << "MultiProcessLock is already locked - " << name_; + return true; + } + + if (name_.length() > MULTI_PROCESS_LOCK_NAME_MAX_LEN) { + LOG(ERROR) << "Socket name too long - " << name_; + return false; + } + + string16 wname = UTF8ToUTF16(name_); + event_.Set(CreateEvent(NULL, FALSE, FALSE, wname.c_str())); + if (event_.Get() && GetLastError() != ERROR_ALREADY_EXISTS) { + return true; + } else { + event_.Set(NULL); + return false; + } + } + + virtual void Unlock() { + if (event_.Get() == NULL) { + DLOG(ERROR) << "Over-unlocked MultiProcessLock - " << name_; + return; + } + event_.Set(NULL); + } + + private: + std::string name_; + base::win::ScopedHandle event_; + DISALLOW_COPY_AND_ASSIGN(MultiProcessLockWin); +}; + +MultiProcessLock* MultiProcessLock::Create(const std::string &name) { + return new MultiProcessLockWin(name); +} |