diff options
author | David 'Digit' Turner <digit@google.com> | 2014-03-10 15:26:00 +0100 |
---|---|---|
committer | David 'Digit' Turner <digit@google.com> | 2014-03-11 18:02:57 +0100 |
commit | 47752bc9b62d974d82ba10bfc3633b72d10afcbd (patch) | |
tree | e5dbd1b3f20ff54654e6f5f2db2a0805431a7845 | |
parent | aac93f1585bc9bdf8a57b6ed3c47c24f56a3990a (diff) | |
download | sdk-47752bc9b62d974d82ba10bfc3633b72d10afcbd.zip sdk-47752bc9b62d974d82ba10bfc3633b72d10afcbd.tar.gz sdk-47752bc9b62d974d82ba10bfc3633b72d10afcbd.tar.bz2 |
emulator/opengl: Remove Android-specific thread_store.
This patch removes the use of the 'thread_store' class from
<utils/threads.h> by providing its own implementation instead
under shared/emugl/common/thread_store.h, plus appropriate
unit tests.
Note that unlike the Android version, this properly destroys
the thread-local values on thread exit (instead of leaking
them).
+ Provide a LazyInstance class used to perform thread-safe
lazy initialization of static variables without the use
of C++ constructors.
Change-Id: Iabe01fbd713c6872b5fe245d7255c3c03749a88a
13 files changed, 990 insertions, 37 deletions
diff --git a/emulator/opengl/host/libs/Translator/EGL/EglThreadInfo.cpp b/emulator/opengl/host/libs/Translator/EGL/EglThreadInfo.cpp index 1b403f2..6481774 100644 --- a/emulator/opengl/host/libs/Translator/EGL/EglThreadInfo.cpp +++ b/emulator/opengl/host/libs/Translator/EGL/EglThreadInfo.cpp @@ -16,26 +16,33 @@ #include "EglThreadInfo.h" #include "EglOsApi.h" -EglThreadInfo::EglThreadInfo():m_err(EGL_SUCCESS),m_api(EGL_OPENGL_ES_API) {} +#include "emugl/common/lazy_instance.h" +#include "emugl/common/thread_store.h" -#include <cutils/threads.h> +namespace { -static thread_store_t s_tls = THREAD_STORE_INITIALIZER; - -static void tlsDestruct(void *ptr) -{ - if (ptr) { - EglThreadInfo *ti = (EglThreadInfo *)ptr; - delete ti; +class EglThreadInfoStore : public emugl::ThreadStore { +public: + EglThreadInfoStore() : emugl::ThreadStore(&destructor) {} +private: + static void destructor(void* value) { + delete static_cast<EglThreadInfo*>(value); } -} +}; + +} // namespace + +EglThreadInfo::EglThreadInfo() : + m_err(EGL_SUCCESS), m_api(EGL_OPENGL_ES_API) {} + +static emugl::LazyInstance<EglThreadInfoStore> s_tls = LAZY_INSTANCE_INIT; EglThreadInfo* EglThreadInfo::get(void) { - EglThreadInfo *ti = (EglThreadInfo *)thread_store_get(&s_tls); + EglThreadInfo *ti = static_cast<EglThreadInfo*>(s_tls->get()); if (!ti) { ti = new EglThreadInfo(); - thread_store_set(&s_tls, ti, tlsDestruct); + s_tls->set(ti); } return ti; } diff --git a/emulator/opengl/host/libs/Translator/EGL/ThreadInfo.cpp b/emulator/opengl/host/libs/Translator/EGL/ThreadInfo.cpp index 4f5d75f..1571b3a 100644 --- a/emulator/opengl/host/libs/Translator/EGL/ThreadInfo.cpp +++ b/emulator/opengl/host/libs/Translator/EGL/ThreadInfo.cpp @@ -14,16 +14,47 @@ * limitations under the License. */ -#include <stdio.h> #include "ThreadInfo.h" -//#define TRACE_THREADINFO -#ifdef TRACE_THREADINFO +#include "emugl/common/lazy_instance.h" +#include "emugl/common/thread_store.h" + +#include <stdio.h> + +// Set TRACE_THREADINFO to 1 to debug creation/destruction of ThreadInfo +// instances. +#define TRACE_THREADINFO 0 + +#if TRACE_THREADINFO #define LOG_THREADINFO(x...) fprintf(stderr, x) #else #define LOG_THREADINFO(x...) #endif +namespace { + +class ThreadInfoStore : public ::emugl::ThreadStore { +public: + ThreadInfoStore() : ::emugl::ThreadStore(&destructor) {} + + size_t getInstanceCount() const { return mNumInstances; } + +private: + static void destructor(void* value) { + LOG_THREADINFO("%s: EFL %p (%d instances)\n", __FUNCTION__, + value, mNumInstances); + delete static_cast<ThreadInfo*>(value); + mNumInstances--; + } + + static size_t mNumInstances; +}; + +size_t ThreadInfoStore::mNumInstances = 0; + +} // namespace + + void ThreadInfo::updateInfo(ContextPtr eglCtx, EglDisplay* dpy, GLEScontext* glesCtx, @@ -37,27 +68,16 @@ void ThreadInfo::updateInfo(ContextPtr eglCtx, objManager = manager; } -#include <cutils/threads.h> -static thread_store_t s_tls = THREAD_STORE_INITIALIZER; -static int active_instance = 0; -static void tlsDestruct(void *ptr) -{ - active_instance--; - LOG_THREADINFO("tlsDestruct EGL %lx %d\n", (long)ptr, active_instance); - if (ptr) { - ThreadInfo *ti = (ThreadInfo *)ptr; - delete ti; - } -} +static ::emugl::LazyInstance<ThreadInfoStore> s_tls = LAZY_INSTANCE_INIT; ThreadInfo *getThreadInfo() { - ThreadInfo *ti = (ThreadInfo *)thread_store_get(&s_tls); + ThreadInfo *ti = static_cast<ThreadInfo*>(s_tls->get()); if (!ti) { ti = new ThreadInfo(); - thread_store_set(&s_tls, ti, tlsDestruct); - active_instance++; - LOG_THREADINFO("getThreadInfo EGL %lx %d\n", (long)ti, active_instance); + s_tls->set(ti); + LOG_THREADINFO("%s: EGL %p (%d instances)\n", __FUNCTION__, + ti, (int)ThreadInfoStore::getInstanceCount()); } return ti; } diff --git a/emulator/opengl/host/libs/libOpenglRender/ThreadInfo.cpp b/emulator/opengl/host/libs/libOpenglRender/ThreadInfo.cpp index 566ca40..5337009 100644 --- a/emulator/opengl/host/libs/libOpenglRender/ThreadInfo.cpp +++ b/emulator/opengl/host/libs/libOpenglRender/ThreadInfo.cpp @@ -15,17 +15,28 @@ */ #include "ThreadInfo.h" -#include <cutils/threads.h> +#include "emugl/common/lazy_instance.h" +#include "emugl/common/thread_store.h" -static thread_store_t s_tls = THREAD_STORE_INITIALIZER; +namespace { + +class ThreadInfoStore : public ::emugl::ThreadStore { +public: + ThreadInfoStore() : ::emugl::ThreadStore(NULL) {} +}; + +} // namespace + +static ::emugl::LazyInstance<ThreadInfoStore> s_tls = LAZY_INSTANCE_INIT; RenderThreadInfo::RenderThreadInfo() { - thread_store_set(&s_tls, this, NULL); + s_tls->set(this); } RenderThreadInfo::~RenderThreadInfo() { + s_tls->set(NULL); } RenderThreadInfo* RenderThreadInfo::get() { - return (RenderThreadInfo*)thread_store_get(&s_tls); + return static_cast<RenderThreadInfo*>(s_tls->get()); } diff --git a/emulator/opengl/shared/OpenglOsUtils/Android.mk b/emulator/opengl/shared/OpenglOsUtils/Android.mk index 8a6c18b..d8807d0 100644 --- a/emulator/opengl/shared/OpenglOsUtils/Android.mk +++ b/emulator/opengl/shared/OpenglOsUtils/Android.mk @@ -10,6 +10,7 @@ LOCAL_PATH := $(call my-dir) host_common_SRC_FILES := osDynLibrary.cpp host_common_LDLIBS := +host_common_INCLUDES := $(LOCAL_PATH) ifeq ($(HOST_OS),windows) host_common_SRC_FILES += \ @@ -29,15 +30,17 @@ endif ### 32-bit host library #### $(call emugl-begin-host-static-library,libOpenglOsUtils) - $(call emugl-export,C_INCLUDES,$(LOCAL_PATH)) + $(call emugl-export,C_INCLUDES,$(host_common_INCLUDES)) LOCAL_SRC_FILES = $(host_common_SRC_FILES) $(call emugl-export,LDLIBS,$(host_common_LDLIBS)) + $(call emugl-import,libemugl_common) $(call emugl-end-module) ### 64-bit host library #### $(call emugl-begin-host-static-library,lib64OpenglOsUtils) - $(call emugl-export,C_INCLUDES,$(LOCAL_PATH)) + $(call emugl-export,C_INCLUDES,$(host_common_INCLUDES)) LOCAL_SRC_FILES = $(host_common_SRC_FILES) $(call emugl-export,LDLIBS,$(host_common_LDLIBS)) $(call emugl-export,CFLAGS,-m64) + $(call emugl-import,lib64emugl_common) $(call emugl-end-module) diff --git a/emulator/opengl/shared/OpenglOsUtils/osThreadUnix.cpp b/emulator/opengl/shared/OpenglOsUtils/osThreadUnix.cpp index 8cd73a9..ef2bebc 100644 --- a/emulator/opengl/shared/OpenglOsUtils/osThreadUnix.cpp +++ b/emulator/opengl/shared/OpenglOsUtils/osThreadUnix.cpp @@ -15,6 +15,8 @@ */ #include "osThread.h" +#include "emugl/common/thread_store.h" + #include <stdint.h> namespace osUtils { @@ -88,6 +90,7 @@ Thread::thread_main(void *p_arg) self->m_exitStatus = ret; pthread_mutex_unlock(&self->m_lock); + ::emugl::ThreadStore::OnThreadExit(); return (void*)(uintptr_t)ret; } diff --git a/emulator/opengl/shared/OpenglOsUtils/osThreadWin.cpp b/emulator/opengl/shared/OpenglOsUtils/osThreadWin.cpp index 2d563f8..1a30b97 100644 --- a/emulator/opengl/shared/OpenglOsUtils/osThreadWin.cpp +++ b/emulator/opengl/shared/OpenglOsUtils/osThreadWin.cpp @@ -15,6 +15,8 @@ */ #include "osThread.h" +#include "emugl/common/thread_store.h" + namespace osUtils { Thread::Thread() : @@ -95,6 +97,7 @@ Thread::thread_main(void *p_arg) Thread *self = (Thread *)p_arg; int ret = self->Main(); self->m_isRunning = false; + ::emugl::ThreadStore::OnThreadExit(); return ret; } diff --git a/emulator/opengl/shared/emugl/common/Android.mk b/emulator/opengl/shared/emugl/common/Android.mk index ae1c213..f1c20b5 100644 --- a/emulator/opengl/shared/emugl/common/Android.mk +++ b/emulator/opengl/shared/emugl/common/Android.mk @@ -6,7 +6,9 @@ LOCAL_PATH := $(call my-dir) ### emugl_common host library ########################################### commonSources := \ + lazy_instance.cpp \ smart_ptr.cpp \ + thread_store.cpp \ host_commonSources := $(commonSources) @@ -19,11 +21,14 @@ LOCAL_SRC_FILES := $(host_commonSources) $(call emugl-export,CFLAGS,-m64) $(call emugl-end-module) + ### emugl_common_unittests ############################################## host_commonSources := \ + lazy_instance_unittest.cpp \ mutex_unittest.cpp \ smart_ptr_unittest.cpp \ + thread_store_unittest.cpp \ $(call emugl-begin-host-executable,emugl_common_host_unittests) LOCAL_SRC_FILES := $(host_commonSources) diff --git a/emulator/opengl/shared/emugl/common/lazy_instance.cpp b/emulator/opengl/shared/emugl/common/lazy_instance.cpp new file mode 100644 index 0000000..ee715fa --- /dev/null +++ b/emulator/opengl/shared/emugl/common/lazy_instance.cpp @@ -0,0 +1,101 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "emugl/common/lazy_instance.h" + +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN 1 +# include <windows.h> +#else +# include <sched.h> +#endif + +namespace emugl { +namespace internal { + +typedef LazyInstanceState::AtomicType AtomicType; + +#if defined(__GNUC__) +static inline void compilerBarrier() { + __asm__ __volatile__ ("" : : : "memory"); +} +#else +#error "Your compiler is not supported" +#endif + +#if defined(__i386__) || defined(__x86_64__) +# define acquireBarrier() compilerBarrier() +# define releaseBarrier() compilerBarrier() +#else +# error "Your CPU is not supported" +#endif + +static inline AtomicType loadAcquire(AtomicType volatile* ptr) { + AtomicType ret = *ptr; + acquireBarrier(); + return ret; +} + +static inline void storeRelease(AtomicType volatile* ptr, AtomicType value) { + releaseBarrier(); + *ptr = value; +} + +static int atomicCompareAndSwap(AtomicType volatile* ptr, + int expected, + int value) { +#ifdef _WIN32 + return InterlockedCompareExchange(ptr, value, expected); +#elif defined(__GNUC__) + return __sync_val_compare_and_swap(ptr, expected, value); +#else +#error "Your compiler is not supported" +#endif +} + +static void yieldThread() { +#ifdef _WIN32 + ::Sleep(0); +#else + sched_yield(); +#endif +} + +bool LazyInstanceState::inInitState() { + return loadAcquire(&mState) == STATE_INIT; +} + +bool LazyInstanceState::needConstruction() { + AtomicType state = loadAcquire(&mState); + if (mState == STATE_DONE) + return false; + + state = atomicCompareAndSwap(&mState, STATE_INIT, STATE_CONSTRUCTING); + if (state == STATE_INIT) + return true; + + do { + yieldThread(); + state = loadAcquire(&mState); + } while (state != STATE_DONE); + + return false; +} + +void LazyInstanceState::doneConstructing() { + storeRelease(&mState, STATE_DONE); +} + +} // namespace internal +} // namespace emugl diff --git a/emulator/opengl/shared/emugl/common/lazy_instance.h b/emulator/opengl/shared/emugl/common/lazy_instance.h new file mode 100644 index 0000000..6641c93 --- /dev/null +++ b/emulator/opengl/shared/emugl/common/lazy_instance.h @@ -0,0 +1,156 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef EMUGL_COMMON_LAZY_INSTANCE_H +#define EMUGL_COMMON_LAZY_INSTANCE_H + +#include <new> + +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN 1 +# include <windows.h> +#endif + +namespace emugl { +namespace internal { + +// A LazyInstance is a helper template that can be used to perform +// thread-safe lazy initialization of static C++ objects without forcing +// the generation of C++ static constructors in the final executable. +// +// In a nutshell, you can replace a statement like: +// +// static Foo gFoo; +// +// With: +// +// static LazyInstance<Foo> gFoo = LAZY_INSTANCE_INIT; +// +// In the first case, a hidden static C++ constructor is embedded in the +// final executable, and executed at *load* *time* to call the Foo::Foo +// constructor on the gFoo object. +// +// On the second case, gFoo will only be initialized lazily, i.e. the first +// time any code actually tries to access the variable. +// +// Note that access is slightly different, i.e.: +// +// gFoo.get() returns a reference to the lazy-initialized object. +// gFoo.ptr() returns a pointer to it. +// gFoo->Something() is equivalent to doing gFoo.ptr()->Something(). +// +// 'gFoo' is stored in the .bss section and this doesn't use heap allocation. +// This class can only be used to perform lazy initialization through the +// class' default constructor. For more specialized cases, you will have +// to create a derived class, e.g.: +// +// class FoorWithDefaultParams : public Foo { +// public: +// FooWithDefaultParams() : Foo(<default-parameters>) {} +// }; +// +// LazyInstance<FooWithDefaultParams> gFoo = LAZY_INSTANCE_INIT; +// +// The implementation of LazyInstance relies on atomic operations and +// POD-struct class definitions, i.e. one that doesn't have any constructor, +// destructor, virtual members, or private ones, and that can be +// zero-initialized at link time. +// +// You can also use LazyInstance<> instances as static local variables, +// e.g.: +// +// Foo* getFooSingleton() { +// static LazyInstance<Foo> sFoo = LAZY_INSTANCE_INIT; +// return sFoo.ptr(); +// } +// +// This is useful on Windows which doesn't support thread-safe lazy +// initialization of static C++ local variables, or when the code is +// compiled with -fno-threadsafe-statics. +// +// This class is heavily inspired by Chromium's implementation of the +// same-named class (see $CHROMIUM/src/base/lazy_instance.h). + +// Atomic state variable type. Used to ensure to synchronize concurrent +// initialization and access without incurring the full cost of a mutex +// lock/unlock. +struct LazyInstanceState { + enum { + STATE_INIT = 0, + STATE_CONSTRUCTING = 1, + STATE_DONE = 2, + }; + + bool inInitState(); + bool needConstruction(); + void doneConstructing(); + +#ifdef _WIN32 + typedef LONG volatile AtomicType; +#else + typedef int volatile AtomicType; +#endif + + volatile AtomicType mState; +}; + +#define LAZY_INSTANCE_STATE_INIT \ + { ::emugl::internal::LazyInstanceState::STATE_INIT } + +} // namespace internal + +// LazyInstance template definition, see comment above for usage +// instructions. It is crucial to make this a POD-struct compatible +// type [1]. +// +// [1] http://en.wikipedia.org/wiki/Plain_Old_Data_Structures +// +template <class T> +struct LazyInstance { + bool hasInstance() const { return !mState.inInitState(); } + + T& get() const { return *ptr(); } + + T* ptr() const; + + const T* operator->() const { return ptr(); } + + T* operator->() { return ptr(); } + + T& operator*() { return get(); } + + // Really private, do not use. + union { + mutable internal::LazyInstanceState mState; + double mPadding; + }; + mutable char mStorage[sizeof(T)]; +}; + +// Initialization value, must resolve to all-0 to ensure the object +// instance is actually placed in the .bss +#define LAZY_INSTANCE_INIT { { LAZY_INSTANCE_STATE_INIT }, { 0 } } + +template <class T> +T* LazyInstance<T>::ptr() const { + if (mState.needConstruction()) { + new (mStorage) T(); + mState.doneConstructing(); + } + return reinterpret_cast<T*>(mStorage); +} + +} // namespace emugl + +#endif // EMUGL_COMMON_LAZY_INSTANCE_H diff --git a/emulator/opengl/shared/emugl/common/lazy_instance_unittest.cpp b/emulator/opengl/shared/emugl/common/lazy_instance_unittest.cpp new file mode 100644 index 0000000..824845f --- /dev/null +++ b/emulator/opengl/shared/emugl/common/lazy_instance_unittest.cpp @@ -0,0 +1,146 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "emugl/common/lazy_instance.h" + +#include "emugl/common/mutex.h" +#include "emugl/common/testing/test_thread.h" + +#include <gtest/gtest.h> + +namespace emugl { + +namespace { + +class Foo { +public: + Foo() : mValue(42) {} + int get() const { return mValue; } + void set(int value) { mValue = value; } + ~Foo() { mValue = 13; } +private: + int mValue; +}; + +class StaticCounter { +public: + StaticCounter() { + Mutex::AutoLock lock(mMutex); + mCounter++; + } + + int getValue() const { + Mutex::AutoLock lock(mMutex); + return mCounter; + } + +private: + static Mutex mMutex; + static int mCounter; +}; + +// NOTE: This introduces a static C++ constructor for this object file, +// but that's ok because a LazyInstance<Mutex> should not be used to +// test the behaviour of LazyInstance :-) +Mutex StaticCounter::mMutex; +int StaticCounter::mCounter = 0; + +} // namespace + +TEST(LazyInstance, HasInstance) { + LazyInstance<Foo> foo_instance = LAZY_INSTANCE_INIT; + EXPECT_FALSE(foo_instance.hasInstance()); + EXPECT_FALSE(foo_instance.hasInstance()); + foo_instance.ptr(); + EXPECT_TRUE(foo_instance.hasInstance()); +} + +TEST(LazyInstance, Simple) { + LazyInstance<Foo> foo_instance = LAZY_INSTANCE_INIT; + Foo* foo1 = foo_instance.ptr(); + EXPECT_TRUE(foo1); + EXPECT_EQ(42, foo_instance->get()); + foo1->set(10); + EXPECT_EQ(10, foo_instance->get()); + EXPECT_EQ(foo1, foo_instance.ptr()); +} + +// For the following test, launch 1000 threads that each try to get +// the instance pointer of a lazy instance. Then verify that they're all +// the same value. +// +// The lazy instance has a special constructor that will increment a +// global counter. This allows us to ensure that it is only called once. +// + +namespace { + +// The following is the shared structure between all threads. +struct MultiState { + MultiState(LazyInstance<StaticCounter>* staticCounter) : + mMutex(), mStaticCounter(staticCounter), mCount(0) {} + + enum { + kMaxThreads = 1000, + }; + + Mutex mMutex; + LazyInstance<StaticCounter>* mStaticCounter; + size_t mCount; + void* mValues[kMaxThreads]; + TestThread* mThreads[kMaxThreads]; +}; + +// The thread function for the test below. +static void* threadFunc(void* param) { + MultiState* state = static_cast<MultiState*>(param); + Mutex::AutoLock lock(state->mMutex); + if (state->mCount < MultiState::kMaxThreads) { + state->mValues[state->mCount++] = state->mStaticCounter->ptr(); + } + return NULL; +} + +} // namespace + +TEST(LazyInstance, MultipleThreads) { + LazyInstance<StaticCounter> counter_instance = LAZY_INSTANCE_INIT; + MultiState state(&counter_instance); + const size_t kNumThreads = MultiState::kMaxThreads; + + // Create all threads. + for (size_t n = 0; n < kNumThreads; ++n) { + state.mThreads[n] = new TestThread(threadFunc, &state); + } + + // Wait for their completion. + for (size_t n = 0; n < kNumThreads; ++n) { + state.mThreads[n]->join(); + } + + // Now check that the constructor was only called once. + EXPECT_EQ(1, counter_instance->getValue()); + + // Now compare all the store values, they should be the same. + StaticCounter* expectedValue = counter_instance.ptr(); + for (size_t n = 0; n < kNumThreads; ++n) { + EXPECT_EQ(expectedValue, state.mValues[n]) << "For thread " << n; + } + + for (size_t n = 0; n < kNumThreads; ++n) { + delete state.mThreads[n]; + } +} + +} // namespace emugl diff --git a/emulator/opengl/shared/emugl/common/thread_store.cpp b/emulator/opengl/shared/emugl/common/thread_store.cpp new file mode 100644 index 0000000..ea64c6f --- /dev/null +++ b/emulator/opengl/shared/emugl/common/thread_store.cpp @@ -0,0 +1,242 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "emugl/common/thread_store.h" + +#ifdef _WIN32 +#include "emugl/common/lazy_instance.h" +#endif + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +// Set to 1 to print debug messages. +#define DEBUG_THREAD_STORE 0 + +#if DEBUG_THREAD_STORE +# define D(...) do { printf("%s:%d: ", __FUNCTION__, __LINE__); printf(__VA_ARGS__); fflush(stdout); } while (0) +#else +# define D(...) ((void)0) +#endif + +namespace emugl { + +#ifdef _WIN32 + +namespace { + +// The ThreadStore implementation on Windows is very tricky, because +// TlsAlloc() doesn't allow one to provide a destructor function. As +// such threads are expected to destroy all TLS values explicitely. +// +// To solve this issue, this source file provides a static method called +// ThreadStore::OnThreadExit() that must be called when a thread exits, +// which will cleanup all values for the current thread. +// +// But this forces us to track thread-specific values ourselves. + +// Maximum amount of thread-specific slots supported by this implementation. +enum { + kMaxTlsSlots = 64 +}; + +// TlsSlotArray is a thread-specific array of values. Instances will +// be stored in a Win32 TLS value controlled by a single master TLS +// key. +// +typedef void* TlsSlotArray[kMaxTlsSlots]; + +// Global state shared by all threads +class GlobalState { +public: + GlobalState() { + D("Entering\n"); + mMasterTls = TlsAlloc(); + D("Master TLS = %d\n", (int)mMasterTls); + InitializeCriticalSection(&mSection); + mLastIndex = 0; + ::memset(mDestructors, 0, sizeof(mDestructors)); + D("Exiting\n"); + } + + // Register a new TLS key, or return -1 on error (too many keys). + // |destroy| is the destructor function for the key. + int registerKey(ThreadStore::Destructor* destroy) { + D("Entering destroy=%p\n", destroy); + int ret = -1; + EnterCriticalSection(&mSection); + if (mLastIndex < kMaxTlsSlots) { + ret = mLastIndex++; + mDestructors[ret] = destroy; + } + LeaveCriticalSection(&mSection); + D("Exiting newKey=%d\n", ret); + return ret; + } + + void unregisterKey(int key) { + D("key=%d\n", key); + if (key < 0 || key >= kMaxTlsSlots) { + D("Invalid key\n"); + return; + } + + // Note: keys are not reusable, but remove the destructor to avoid + // crashes in leaveCurrentThread() when it points to a function that + // is going to be unloaded from the process' address space. + EnterCriticalSection(&mSection); + mDestructors[key] = NULL; + LeaveCriticalSection(&mSection); + D("Exiting\n"); + } + + // Get the current thread-local value for a given |key|. + void* getValue(int key) const { + D("Entering key=%d\n", key); + if (key < 0 || key >= kMaxTlsSlots) { + D("Invalid key, result=NULL\n"); + return NULL; + } + + TlsSlotArray* array = getArray(); + void* ret = (*array)[key]; + D("Exiting keyValue=%p\n", ret); + return ret; + } + + // Set the current thread-local |value| for a given |key|. + void setValue(int key, void* value) { + D("Entering key=%d\n",key); + if (key < 0 || key >= kMaxTlsSlots) { + D("Invalid key, returning\n"); + return; + } + + TlsSlotArray* array = getArray(); + (*array)[key] = value; + D("Exiting\n"); + } + + // Call this when a thread exits to destroy all its thread-local values. + void leaveCurrentThread() { + D("Entering\n"); + TlsSlotArray* array = + reinterpret_cast<TlsSlotArray*>(TlsGetValue(mMasterTls)); + if (!array) { + D("Exiting, no thread-local data in this thread\n"); + return; + } + + for (size_t n = 0; n < kMaxTlsSlots; ++n) { + void* value = array[n]; + if (value) { + (*array)[n] = NULL; + // NOTE: In theory, a destructor could reset the slot to + // a new value, and we would have to loop in this function + // in interesting ways. In practice, ignore the issue. + EnterCriticalSection(&mSection); + ThreadStore::Destructor* destroy = mDestructors[n]; + LeaveCriticalSection(&mSection); + if (destroy) { + D("Calling destructor %p for key=%d, with value=%p\n", + destroy, (int)n, value); + (*destroy)(value); + } + } + } + TlsSetValue(mMasterTls, NULL); + ::free(array); + D("Exiting\n"); + } + +private: + // Return the thread-local array of TLS slots for the current thread. + // Cannot return NULL. + TlsSlotArray* getArray() const { + D("Entering\n"); + TlsSlotArray* array = + reinterpret_cast<TlsSlotArray*>(TlsGetValue(mMasterTls)); + if (!array) { + array = reinterpret_cast<TlsSlotArray*>( + ::calloc(sizeof(*array), 1)); + TlsSetValue(mMasterTls, array); + D("Allocated new array at %p\n", array); + } else { + D("Retrieved array at %p\n", array); + } + return array; + } + + DWORD mMasterTls; + CRITICAL_SECTION mSection; + int mLastIndex; + ThreadStore::Destructor* mDestructors[kMaxTlsSlots]; +}; + +LazyInstance<GlobalState> gGlobalState = LAZY_INSTANCE_INIT; + +} // namespace + +ThreadStore::ThreadStore(Destructor* destroy) { + D("Entering this=%p destroy=%p\n", this, destroy); + mKey = gGlobalState->registerKey(destroy); + D("Exiting this=%p key=%d\n", this, mKey); +} + +ThreadStore::~ThreadStore() { + D("Entering this=%p\n", this); + GlobalState* state = gGlobalState.ptr(); + state->unregisterKey(mKey); + D("Exiting this=%p\n", this); +} + +void* ThreadStore::get() const { + D("Entering this=%p\n", this); + void* ret = gGlobalState->getValue(mKey); + D("Exiting this=%p value=%p\n", this, ret); + return ret; +} + +void ThreadStore::set(void* value) { + D("Entering this=%p value=%p\n", this, value); + gGlobalState->setValue(mKey, value); + D("Exiting this=%p\n", this); +} + +// static +void ThreadStore::OnThreadExit() { + gGlobalState->leaveCurrentThread(); +} + +#else // !_WIN32 + +ThreadStore::ThreadStore(Destructor* destroy) { + int ret = pthread_key_create(&mKey, destroy); + if (ret != 0) { + fprintf(stderr, + "Could not create thread store key: %s\n", + strerror(ret)); + exit(1); + } +} + +ThreadStore::~ThreadStore() { + pthread_key_delete(mKey); +} + +#endif // !_WIN32 + +} // namespace emugl diff --git a/emulator/opengl/shared/emugl/common/thread_store.h b/emulator/opengl/shared/emugl/common/thread_store.h new file mode 100644 index 0000000..5fd08bd --- /dev/null +++ b/emulator/opengl/shared/emugl/common/thread_store.h @@ -0,0 +1,110 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef EMUGL_COMMON_THREAD_STORE_H +#define EMUGL_COMMON_THREAD_STORE_H + +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN 1 +# include <windows.h> +#else +# include <pthread.h> +#endif + +namespace emugl { + +// A class to model storage of thread-specific values, that can be +// destroyed on thread exit. +// +// Note that on Windows, a thread must call OnThreadExit() explicitly +// here to ensure that the values are probably discarded. This is an +// unfortunate requirement of the Win32 API, which doesn't support +// destructors at all. +// +// There are various hacks on the web to try to achieve this automatically +// (e.g. [1]) but they rely on using the Microsoft build tools, +// which doesn't work for us. +// +// Note another important issue with ThreadStore instances: if you create +// one instance in a shared library, you need to make sure that it is +// always destroyed before the library is unloaded. Otherwise, future +// thread exit will likely crash, due to calling a destructor function +// that is no longer in the process' address space. +// +// Finally, destroying an instance does _not_ free the corresponding values, +// because doing so properly requires coordinating all participating threads, +// which is impossible to achieve in the most general case. Thus, consider +// that thread-local values are always leaked on library unload, or on +// program exit. +// +// [1] http://stackoverflow.com/questions/14538159/about-tls-callback-in-windows + +class ThreadStore { +public: + // Type of a function used to destroy a thread-specific value that + // was previously assigned by calling set(). + typedef void (Destructor)(void* value); + + // Initialize instance so that is hold keys that must be destroyed + // on thread exit by calling |destroy|. + explicit ThreadStore(Destructor* destroy); + + // NOTE: Destructor don't free the thread-local values, but are required + // to avoid crashes (see note above). + ~ThreadStore(); + + // Retrieve current thread-specific value from store. +#ifdef _WIN32 + void* get() const; +#else + inline void* get() const { + return pthread_getspecific(mKey); + } +#endif + + // Set the new thread-specific value. +#ifdef _WIN32 + void set(void* value); +#else + inline void set(void* value) { + pthread_setspecific(mKey, value); + } +#endif + +#ifdef _WIN32 + // Each thread should call this function on exit to ensure that + // all corresponding TLS values are properly freed. + static void OnThreadExit(); +#else + // Nothing to do on Posix. + static inline void OnThreadExit() {} +#endif + +private: + // Ensure you can't create an empty ThreadStore instance, or simply + // copy it in any way. + ThreadStore(); + ThreadStore(const ThreadStore&); + ThreadStore& operator=(const ThreadStore&); + +#ifdef _WIN32 + int mKey; +#else + pthread_key_t mKey; +#endif +}; + +} // namespace emugl + +#endif // EMUGL_COMMON_THREAD_STORE_H diff --git a/emulator/opengl/shared/emugl/common/thread_store_unittest.cpp b/emulator/opengl/shared/emugl/common/thread_store_unittest.cpp new file mode 100644 index 0000000..6b5dddb --- /dev/null +++ b/emulator/opengl/shared/emugl/common/thread_store_unittest.cpp @@ -0,0 +1,146 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "emugl/common/thread_store.h" + +#include "emugl/common/mutex.h" +#include "emugl/common/testing/test_thread.h" + +#include <gtest/gtest.h> + +namespace emugl { + +namespace { + +// Helper class used to count instance creation and destruction. +class StaticCounter { +public: + enum { + kMaxInstances = 1000, + }; + + StaticCounter() { + Mutex::AutoLock lock(mMutex); + if (mCreationCount < kMaxInstances) + mInstances[mCreationCount] = this; + mCreationCount++; + } + + ~StaticCounter() { + Mutex::AutoLock lock(mMutex); + mDestructionCount++; + } + + static void reset() { + Mutex::AutoLock lock(mMutex); + mCreationCount = 0; + mDestructionCount = 0; + } + + static size_t getCreationCount() { + Mutex::AutoLock lock(mMutex); + return mCreationCount; + } + + static size_t getDestructionCount() { + Mutex::AutoLock lock(mMutex); + return mDestructionCount; + } + + static void freeAll() { + for (size_t n = 0; n < kMaxInstances; ++n) + delete mInstances[n]; + } + +private: + static Mutex mMutex; + static size_t mCreationCount; + static size_t mDestructionCount; + static StaticCounter* mInstances[kMaxInstances]; +}; + +Mutex StaticCounter::mMutex; +size_t StaticCounter::mCreationCount = 0; +size_t StaticCounter::mDestructionCount = 0; +StaticCounter* StaticCounter::mInstances[kMaxInstances]; + +} // namespace + +// Just check that we can create a new ThreadStore with an empty +// destructor, and use it in the current thread. +TEST(ThreadStore, MainThreadWithoutDestructor) { + ThreadStore store(NULL); + static int x = 42; + store.set(&x); + EXPECT_EQ(&x, store.get()); +} + +// The following test checks that exiting a thread correctly deletes +// any thread-local value stored in it. +static void simplyDestroy(void* value) { + delete (StaticCounter*) value; +} + +static void* simpleThreadFunc(void* param) { + ThreadStore* store = static_cast<ThreadStore*>(param); + store->set(new StaticCounter()); + ThreadStore::OnThreadExit(); + return NULL; +} + +TEST(ThreadStore, ThreadsWithDestructor) { + ThreadStore store(simplyDestroy); + const size_t kNumThreads = 1000; + TestThread* threads[kNumThreads]; + StaticCounter::reset(); + + for (size_t n = 0; n < kNumThreads; ++n) { + threads[n] = new TestThread(&simpleThreadFunc, &store); + } + for (size_t n = 0; n < kNumThreads; ++n) { + threads[n]->join(); + } + + EXPECT_EQ(kNumThreads, StaticCounter::getCreationCount()); + EXPECT_EQ(kNumThreads, StaticCounter::getDestructionCount()); + + for (size_t n = 0; n < kNumThreads; ++n) { + delete threads[n]; + } +} + +TEST(ThreadStore, ThreadsWithoutDestructor) { + ThreadStore store(NULL); + const size_t kNumThreads = 1000; + TestThread* threads[kNumThreads]; + StaticCounter::reset(); + + for (size_t n = 0; n < kNumThreads; ++n) { + threads[n] = new TestThread(&simpleThreadFunc, &store); + } + for (size_t n = 0; n < kNumThreads; ++n) { + threads[n]->join(); + } + + EXPECT_EQ(kNumThreads, StaticCounter::getCreationCount()); + EXPECT_EQ(0U, StaticCounter::getDestructionCount()); + + StaticCounter::freeAll(); + + for (size_t n = 0; n < kNumThreads; ++n) { + delete threads[n]; + } +} + +} // namespace emugl |