Fixes a leak in ThreadLocal.

This commit is contained in:
zhanyong.wan 2010-03-26 20:23:06 +00:00
parent 3569c3c86d
commit b9a7cead1c
2 changed files with 85 additions and 22 deletions

View File

@ -1084,10 +1084,19 @@ extern "C" inline void DeleteThreadLocalValue(void* value_holder) {
// //
// The template type argument T must have a public copy constructor. // The template type argument T must have a public copy constructor.
// In addition, the default ThreadLocal constructor requires T to have // In addition, the default ThreadLocal constructor requires T to have
// a public default constructor. An object managed by a ThreadLocal // a public default constructor.
// instance for a thread is guaranteed to exist at least until the //
// earliest of the two events: (a) the thread terminates or (b) the // An object managed for a thread by a ThreadLocal instance is deleted
// ThreadLocal object is destroyed. // when the thread exits. Or, if the ThreadLocal instance dies in
// that thread, when the ThreadLocal dies. It's the user's
// responsibility to ensure that all other threads using a ThreadLocal
// have exited when it dies, or the per-thread objects for those
// threads will not be deleted.
//
// Google Test only uses global ThreadLocal objects. That means they
// will die after main() has returned. Therefore, no per-thread
// object managed by Google Test will be leaked as long as all threads
// using Google Test have exited when main() returns.
template <typename T> template <typename T>
class ThreadLocal { class ThreadLocal {
public: public:
@ -1097,6 +1106,11 @@ class ThreadLocal {
default_(value) {} default_(value) {}
~ThreadLocal() { ~ThreadLocal() {
// Destroys the managed object for the current thread, if any.
DeleteThreadLocalValue(pthread_getspecific(key_));
// Releases resources associated with the key. This will *not*
// delete managed objects for other threads.
GTEST_CHECK_POSIX_SUCCESS_(pthread_key_delete(key_)); GTEST_CHECK_POSIX_SUCCESS_(pthread_key_delete(key_));
} }
@ -1120,6 +1134,8 @@ class ThreadLocal {
static pthread_key_t CreateKey() { static pthread_key_t CreateKey() {
pthread_key_t key; pthread_key_t key;
// When a thread exits, DeleteThreadLocalValue() will be called on
// the object managed for that thread.
GTEST_CHECK_POSIX_SUCCESS_( GTEST_CHECK_POSIX_SUCCESS_(
pthread_key_create(&key, &DeleteThreadLocalValue)); pthread_key_create(&key, &DeleteThreadLocalValue));
return key; return key;

View File

@ -911,47 +911,93 @@ TEST(ThreadLocalTest, ParameterizedConstructorSetsDefault) {
EXPECT_STREQ("foo", result.c_str()); EXPECT_STREQ("foo", result.c_str());
} }
// DestructorTracker keeps track of whether the class instances have been // DestructorTracker keeps track of whether its instances have been
// destroyed. The static synchronization mutex has to be defined outside // destroyed.
// of the class, due to syntax of its definition.
static GTEST_DEFINE_STATIC_MUTEX_(destructor_tracker_mutex);
static std::vector<bool> g_destroyed; static std::vector<bool> g_destroyed;
class DestructorTracker { class DestructorTracker {
public: public:
DestructorTracker() : index_(GetNewIndex()) {} DestructorTracker() : index_(GetNewIndex()) {}
DestructorTracker(const DestructorTracker& /* rhs */)
: index_(GetNewIndex()) {}
~DestructorTracker() { ~DestructorTracker() {
MutexLock lock(&destructor_tracker_mutex); // We never access g_destroyed concurrently, so we don't need to
// protect the write operation under a mutex.
g_destroyed[index_] = true; g_destroyed[index_] = true;
} }
private: private:
static int GetNewIndex() { static int GetNewIndex() {
MutexLock lock(&destructor_tracker_mutex);
g_destroyed.push_back(false); g_destroyed.push_back(false);
return g_destroyed.size() - 1; return g_destroyed.size() - 1;
} }
const int index_; const int index_;
}; };
template <typename T> typedef ThreadLocal<DestructorTracker>* ThreadParam;
void CallThreadLocalGet(ThreadLocal<T>* threadLocal) {
threadLocal->get(); void CallThreadLocalGet(ThreadParam thread_local) {
thread_local->get();
} }
TEST(ThreadLocalTest, DestroysManagedObjectsNoLaterThanSelf) { // Tests that when a ThreadLocal object dies in a thread, it destroys
// the managed object for that thread.
TEST(ThreadLocalTest, DestroysManagedObjectForOwnThreadWhenDying) {
g_destroyed.clear(); g_destroyed.clear();
{ {
// The next line default constructs a DestructorTracker object as
// the default value of objects managed by thread_local.
ThreadLocal<DestructorTracker> thread_local; ThreadLocal<DestructorTracker> thread_local;
ThreadWithParam<ThreadLocal<DestructorTracker>*> thread( ASSERT_EQ(1U, g_destroyed.size());
&CallThreadLocalGet<DestructorTracker>, &thread_local, NULL); ASSERT_FALSE(g_destroyed[0]);
thread.Join();
// This creates another DestructorTracker object for the main thread.
thread_local.get();
ASSERT_EQ(2U, g_destroyed.size());
ASSERT_FALSE(g_destroyed[0]);
ASSERT_FALSE(g_destroyed[1]);
} }
// Verifies that all DestructorTracker objects there were have been
// destroyed. // Now thread_local has died. It should have destroyed both the
for (size_t i = 0; i < g_destroyed.size(); ++i) // default value shared by all threads and the value for the main
EXPECT_TRUE(g_destroyed[i]) << "at index " << i; // thread.
ASSERT_EQ(2U, g_destroyed.size());
EXPECT_TRUE(g_destroyed[0]);
EXPECT_TRUE(g_destroyed[1]);
g_destroyed.clear();
}
// Tests that when a thread exits, the thread-local object for that
// thread is destroyed.
TEST(ThreadLocalTest, DestroysManagedObjectAtThreadExit) {
g_destroyed.clear();
{
// The next line default constructs a DestructorTracker object as
// the default value of objects managed by thread_local.
ThreadLocal<DestructorTracker> thread_local;
ASSERT_EQ(1U, g_destroyed.size());
ASSERT_FALSE(g_destroyed[0]);
// This creates another DestructorTracker object in the new thread.
ThreadWithParam<ThreadParam> thread(
&CallThreadLocalGet, &thread_local, NULL);
thread.Join();
// Now the new thread has exited. The per-thread object for it
// should have been destroyed.
ASSERT_EQ(2U, g_destroyed.size());
ASSERT_FALSE(g_destroyed[0]);
ASSERT_TRUE(g_destroyed[1]);
}
// Now thread_local has died. The default value should have been
// destroyed too.
ASSERT_EQ(2U, g_destroyed.size());
EXPECT_TRUE(g_destroyed[0]);
EXPECT_TRUE(g_destroyed[1]);
g_destroyed.clear(); g_destroyed.clear();
} }
@ -965,6 +1011,7 @@ TEST(ThreadLocalTest, ThreadLocalMutationsAffectOnlyCurrentThread) {
RunFromThread(&RetrieveThreadLocalValue, make_pair(&thread_local, &result)); RunFromThread(&RetrieveThreadLocalValue, make_pair(&thread_local, &result));
EXPECT_TRUE(result.c_str() == NULL); EXPECT_TRUE(result.c_str() == NULL);
} }
#endif // GTEST_IS_THREADSAFE #endif // GTEST_IS_THREADSAFE
} // namespace internal } // namespace internal