diff options
author | deanm@google.com <deanm@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-08-22 10:49:15 +0000 |
---|---|---|
committer | deanm@google.com <deanm@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-08-22 10:49:15 +0000 |
commit | 578c7cc2380e32ca348b940be71f6b5e3e6c3fea (patch) | |
tree | 8e405703d50d2781f91097e3671efcd278926527 /base | |
parent | e816b3e0b4f97d8c0517cb277b265e07c1978f4f (diff) | |
download | chromium_src-578c7cc2380e32ca348b940be71f6b5e3e6c3fea.zip chromium_src-578c7cc2380e32ca348b940be71f6b5e3e6c3fea.tar.gz chromium_src-578c7cc2380e32ca348b940be71f6b5e3e6c3fea.tar.bz2 |
Create a simple abstraction to a native OS thread, mostly useful for unittesting when you don't want a message loop.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@1217 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base')
-rw-r--r-- | base/SConscript | 2 | ||||
-rw-r--r-- | base/build/base.vcproj | 8 | ||||
-rw-r--r-- | base/build/base_unittests.vcproj | 4 | ||||
-rw-r--r-- | base/platform_thread.h | 8 | ||||
-rw-r--r-- | base/simple_thread.cc | 82 | ||||
-rw-r--r-- | base/simple_thread.h | 162 | ||||
-rw-r--r-- | base/simple_thread_unittest.cc | 108 |
7 files changed, 372 insertions, 2 deletions
diff --git a/base/SConscript b/base/SConscript index a7fc566..88c2020 100644 --- a/base/SConscript +++ b/base/SConscript @@ -77,6 +77,7 @@ input_files = [ 'revocable_store.cc', 'ref_counted.cc', 'sha2.cc', + 'simple_thread.cc', 'stats_table.cc', 'string_escape.cc', 'string_piece.cc', @@ -255,6 +256,7 @@ test_files = [ 'ref_counted_unittest.cc', 'run_all_unittests.cc', 'sha2_unittest.cc', + 'simple_thread_unittest.cc', 'singleton_unittest.cc', 'stack_container_unittest.cc', 'string_escape_unittest.cc', diff --git a/base/build/base.vcproj b/base/build/base.vcproj index 7864bd0..ad52b1d 100644 --- a/base/build/base.vcproj +++ b/base/build/base.vcproj @@ -614,6 +614,14 @@ > </File> <File + RelativePath="..\simple_thread.h" + > + </File> + <File + RelativePath="..\simple_thread.cc" + > + </File> + <File RelativePath="..\singleton.h" > </File> diff --git a/base/build/base_unittests.vcproj b/base/build/base_unittests.vcproj index 2c7908a..29997f4 100644 --- a/base/build/base_unittests.vcproj +++ b/base/build/base_unittests.vcproj @@ -268,6 +268,10 @@ > </File> <File + RelativePath="..\simple_thread_unittest.cc" + > + </File> + <File RelativePath="..\singleton_unittest.cc" > </File> diff --git a/base/platform_thread.h b/base/platform_thread.h index 1274f0f..542716b 100644 --- a/base/platform_thread.h +++ b/base/platform_thread.h @@ -32,6 +32,8 @@ #include "base/basictypes.h" +// PlatformThreadHandle should be a numeric type on all platforms, so it can +// be initialized to 0. However, 0 cannot be assumed to be an invalid handle. #if defined(OS_WIN) typedef void* PlatformThreadHandle; // HANDLE #elif defined(OS_POSIX) @@ -68,14 +70,16 @@ class PlatformThread { // that the default stack size should be used. Upon success, // |*thread_handle| will be assigned a handle to the newly created thread, // and |delegate|'s ThreadMain method will be executed on the newly created - // thread. When you are done with the thread handle, you must call Join to + // thread. + // NOTE: When you are done with the thread handle, you must call Join to // release system resources associated with the thread. You must ensure that // the Delegate object outlives the thread. static bool Create(size_t stack_size, Delegate* delegate, PlatformThreadHandle* thread_handle); // Joins with a thread created via the Create function. This function blocks - // the caller until the designated thread exits. + // the caller until the designated thread exits. This will invalidate + // |thread_handle|. static void Join(PlatformThreadHandle thread_handle); private: diff --git a/base/simple_thread.cc b/base/simple_thread.cc new file mode 100644 index 0000000..797843d --- /dev/null +++ b/base/simple_thread.cc @@ -0,0 +1,82 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/simple_thread.h" + +#include "base/call_wrapper.h" +#include "base/waitable_event.h" +#include "base/logging.h" +#include "base/platform_thread.h" +#include "base/string_util.h" + +namespace base { + +void SimpleThread::Start() { + DCHECK(!HasBeenStarted()) << "Tried to Start a thread multiple times."; + bool success = PlatformThread::Create(options_.stack_size(), this, &thread_); + CHECK(success); + event_.Wait(); // Wait for the thread to complete initialization. +} + +void SimpleThread::Join() { + DCHECK(HasBeenStarted()) << "Tried to Join a never-started thread."; + DCHECK(!HasBeenJoined()) << "Tried to Join a thread multiple times."; + PlatformThread::Join(thread_); + joined_ = true; +} + +void SimpleThread::ThreadMain() { + tid_ = PlatformThread::CurrentId(); + // Construct our full name of the form "name_prefix_/TID". + name_.push_back('/'); + name_.append(IntToString(tid_)); + PlatformThread::SetName(tid_, name_.c_str()); + + // We've initialized our new thread, signal that we're done to Start(). + event_.Signal(); + + Run(); +} + +SimpleThread::~SimpleThread() { + DCHECK(HasBeenStarted()) << "SimpleThread was never started."; + DCHECK(HasBeenJoined()) << "SimpleThread destroyed without being Join()ed."; +} + +void CallWrapperSimpleThread::Run() { + DCHECK(wrapper_) << "Tried to call Run without a wrapper (called twice?)"; + wrapper_->Run(); + wrapper_ = NULL; +} + +CallWrapperSimpleThread::~CallWrapperSimpleThread() { + DCHECK(!wrapper_) << "CallWrapper was never released."; +} + +} // namespace base diff --git a/base/simple_thread.h b/base/simple_thread.h new file mode 100644 index 0000000..fe9a136 --- /dev/null +++ b/base/simple_thread.h @@ -0,0 +1,162 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// WARNING: You should probably be using Thread (thread.h) instead. Thread is +// Chrome's message-loop based Thread abstraction, and if you are a +// thread running in the browser, there will likely be assumptions +// that your thread will have an associated message loop. +// +// This is a simple thread interface that backs to a native operating system +// thread. You should use this only when you want a thread that does not have +// an associated MessageLoop. Unittesting is the best example of this. +// +// The simplest interface to use is CallWrapperSimpleThread, which will create +// a new thread, and execute the CallWrapper in this new thread until it has +// completed, exiting the thread. See call_wrapper.h for that interface. +// +// NOTE: You *MUST* call Join on the thread to clean up the underlying thread +// resources. You are also responsible for destructing the SimpleThread object. +// It is invalid to destroy a SimpleThread while it is running, or without +// Start() having been called (and a thread never created). +// +// Thread Safety: A SimpleThread is not completely thread safe. It is safe to +// access it from the creating thread or from the newly created thread. This +// implies that the creator thread should be the thread that calls Join. +// +// Example: +// CallWrapper* wrapper = NewMethodCallWrapper(obj, &Foo::Main); +// scoped_ptr<SimpleThread> thread(new CallWrapperSimpleThread(wrapper)); +// thread->Start(); +// // Start will return after the Thread has been successfully started and +// // initialized. The newly created thread will invoke obj->Main, and run +// // until it returns. The CallWrapper will then delete itself. +// thread->Join(); // Wait until the thread has exited. You MUST Join! +// // The SimpleThread object is still valid, however you may not call Join +// // or Start again. In this example the scoper will destroy the object. + +#ifndef BASE_SIMPLE_THREAD_H_ +#define BASE_SIMPLE_THREAD_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/waitable_event.h" +#include "base/platform_thread.h" + +class CallWrapper; + +namespace base { + +// This is the base SimpleThread. You can derive from it and implement the +// virtual Run method, or you can use the CallWrapperSimpleThread interface. +class SimpleThread : public PlatformThread::Delegate { + public: + class Options { + public: + Options() : stack_size_(0) { } + ~Options() { } + + // We use the standard compiler-supplied copy constructor. + + // A custom stack size, or 0 for the system default. + void set_stack_size(size_t size) { stack_size_ = size; } + size_t stack_size() const { return stack_size_; } + private: + size_t stack_size_; + }; + + // Create a SimpleThread. |options| should be used to manage any specific + // configuration involving the thread creation and management. + // Every thread has a name, in the form of |name_prefix|/TID, for example + // "my_thread/321". The thread will not be created until Start() is called. + SimpleThread(const Options& options, const std::string& name_prefix) + : name_prefix_(name_prefix), name_(name_prefix_), options_(options), + thread_(0), event_(true, false), tid_(0), joined_(false) { } + + SimpleThread() + : name_prefix_("unnamed"), name_(name_prefix_), + thread_(0), event_(true, false), tid_(0), joined_(false) { } + + virtual ~SimpleThread(); + + virtual void Start(); + virtual void Join(); + + // We follow the PlatformThread Delegate interface. + virtual void ThreadMain(); + + // Subclasses should override the Run method. + virtual void Run() = 0; + + // Return the thread name prefix, or "unnamed" if none was supplied. + std::string name_prefix() { return name_prefix_; } + + // Return the completed name including TID, only valid after Start(). + std::string name() { return name_; } + + // Return the thread id, only valid after Start(). + int tid() { return tid_; } + + // Return True if Start() has ever been called. + bool HasBeenStarted() { return event_.IsSignaled(); } + + // Return True if Join() has evern been called. + bool HasBeenJoined() { return joined_; } + + private: + const std::string name_prefix_; + std::string name_; + const Options options_; + PlatformThreadHandle thread_; // PlatformThread handle, invalid after Join! + WaitableEvent event_; // Signaled if Start() was ever called. + int tid_; // The backing thread's id. + bool joined_; // True if Join has been called. +}; + +class CallWrapperSimpleThread : public SimpleThread { + public: + typedef SimpleThread::Options Options; + + explicit CallWrapperSimpleThread(CallWrapper* wrapper) + : SimpleThread(), wrapper_(wrapper) { } + + CallWrapperSimpleThread(CallWrapper* wrapper, + const Options& options, + const std::string& name_prefix) + : SimpleThread(options, name_prefix), wrapper_(wrapper) { } + + virtual ~CallWrapperSimpleThread(); + virtual void Run(); + private: + CallWrapper* wrapper_; +}; + +} // namespace base + +#endif // BASE_SIMPLE_THREAD_H_ diff --git a/base/simple_thread_unittest.cc b/base/simple_thread_unittest.cc new file mode 100644 index 0000000..c894637 --- /dev/null +++ b/base/simple_thread_unittest.cc @@ -0,0 +1,108 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <vector> + +#include "base/call_wrapper.h" +#include "base/scoped_ptr.h" +#include "base/simple_thread.h" +#include "base/string_util.h" +#include "base/waitable_event.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +void SetInt(int* p, int x) { + *p = x; +} + +void SignalEvent(base::WaitableEvent* event) { + EXPECT_FALSE(event->IsSignaled()); + event->Signal(); + EXPECT_TRUE(event->IsSignaled()); +} + +} // namespace + +TEST(SimpleThreadTest, CreateAndJoin) { + int stack_int = 0; + + CallWrapper* wrapper = NewFunctionCallWrapper(SetInt, &stack_int, 7); + EXPECT_EQ(0, stack_int); + scoped_ptr<base::SimpleThread> thread( + new base::CallWrapperSimpleThread(wrapper)); + EXPECT_FALSE(thread->HasBeenStarted()); + EXPECT_FALSE(thread->HasBeenJoined()); + EXPECT_EQ(0, stack_int); + + thread->Start(); + EXPECT_TRUE(thread->HasBeenStarted()); + EXPECT_FALSE(thread->HasBeenJoined()); + + thread->Join(); + EXPECT_TRUE(thread->HasBeenStarted()); + EXPECT_TRUE(thread->HasBeenJoined()); + EXPECT_EQ(7, stack_int); +} + +TEST(SimpleThreadTest, WaitForEvent) { + // Create a thread, and wait for it to signal us. + base::WaitableEvent event(true, false); + + scoped_ptr<base::SimpleThread> thread(new base::CallWrapperSimpleThread( + NewFunctionCallWrapper(SignalEvent, &event))); + + EXPECT_FALSE(event.IsSignaled()); + thread->Start(); + event.Wait(); + EXPECT_TRUE(event.IsSignaled()); + thread->Join(); +} + +TEST(SimpleThreadTest, Named) { + base::WaitableEvent event(true, false); + + base::SimpleThread::Options options; + scoped_ptr<base::SimpleThread> thread(new base::CallWrapperSimpleThread( + NewFunctionCallWrapper(SignalEvent, &event), options, "testy")); + EXPECT_EQ(thread->name_prefix(), "testy"); + EXPECT_FALSE(event.IsSignaled()); + + thread->Start(); + EXPECT_EQ(thread->name_prefix(), "testy"); + EXPECT_EQ(thread->name(), std::string("testy/") + IntToString(thread->tid())); + event.Wait(); + + EXPECT_TRUE(event.IsSignaled()); + thread->Join(); + + // We keep the name and tid, even after the thread is gone. + EXPECT_EQ(thread->name_prefix(), "testy"); + EXPECT_EQ(thread->name(), std::string("testy/") + IntToString(thread->tid())); +} |