class ThreadLocalStorage {
public:
// Prototype for the TLS destructor function, which can be optionally used to
// cleanup thread local storage on thread exit. 'value' is the data that is
// stored in thread local storage.
typedef void (*TLSDestructorFunc)(void* value);
// A key representing one value stored in TLS.
class Slot {
public:
Slot(TLSDestructorFunc destructor = NULL);
// This constructor should be used for statics.
// It returns an uninitialized Slot.
explicit Slot(base::LinkerInitialized x) {}
// Set up the TLS slot. Called by the constructor.
// 'destructor' is a pointer to a function to perform per-thread cleanup of
// this object. If set to NULL, no cleanup is done for this TLS slot.
// Returns false on error.
bool Initialize(TLSDestructorFunc destructor);
// Free a previously allocated TLS 'slot'.
// If a destructor was set for this slot, removes
// the destructor so that remaining threads exiting
// will not free data.
void Free();
// Get the thread-local value stored in slot 'slot'.
// Values are guaranteed to initially be zero.
void* Get() const;
// Set the thread-local value stored in slot 'slot' to
// value 'value'.
void Set(void* value);
bool initialized() const { return initialized_; }
private:
// The internals of this struct should be considered private.
bool initialized_;
#if defined(OS_WIN)
int slot_;
#elif defined(OS_POSIX)
pthread_key_t key_;
#endif
DISALLOW_COPY_AND_ASSIGN(Slot);
};
#if defined(OS_WIN)
// Function called when on thread exit to call TLS
// destructor functions. This function is used internally.
static void ThreadExit();
private:
// Function to lazily initialize our thread local storage.
static void **Initialize();
private:
// The maximum number of 'slots' in our thread local storage stack.
// For now, this is fixed. We could either increase statically, or
// we could make it dynamic in the future.
static const int kThreadLocalStorageSize = 64;
static long tls_key_;
static long tls_max_;
static TLSDestructorFunc tls_destructors_[kThreadLocalStorageSize];
#endif // OS_WIN
DISALLOW_COPY_AND_ASSIGN(ThreadLocalStorage);
};
// Temporary backwards-compatible name.
// TODO(evanm): replace all usage of TLSSlot.
typedef ThreadLocalStorage::Slot TLSSlot;
ThreadLocalStorage::TLSDestructorFunc
ThreadLocalStorage::tls_destructors_[kThreadLocalStorageSize];
void** ThreadLocalStorage::Initialize() {
if (tls_key_ == TLS_OUT_OF_INDEXES) {
long value = TlsAlloc();
DCHECK(value != TLS_OUT_OF_INDEXES);
// Atomically test-and-set the tls_key. If the key is TLS_OUT_OF_INDEXES,
// go ahead and set it. Otherwise, do nothing, as another
// thread already did our dirty work.
if (InterlockedCompareExchange(&tls_key_, value, TLS_OUT_OF_INDEXES) !=
TLS_OUT_OF_INDEXES) {
// We've been shortcut. Another thread replaced tls_key_ first so we need
// to destroy our index and use the one the other thread got first.
TlsFree(value);
}
}
DCHECK(TlsGetValue(tls_key_) == NULL);
// Create an array to store our data.
void** tls_data = new void*[kThreadLocalStorageSize];
memset(tls_data, 0, sizeof(void*[kThreadLocalStorageSize]));
TlsSetValue(tls_key_, tls_data);
return tls_data;
}
ThreadLocalStorage::Slot::Slot(TLSDestructorFunc destructor)
: initialized_(false) {
Initialize(destructor);
}
bool ThreadLocalStorage::Slot::Initialize(TLSDestructorFunc destructor) {
if (tls_key_ == TLS_OUT_OF_INDEXES || !TlsGetValue(tls_key_))
ThreadLocalStorage::Initialize();
// Grab a new slot.
slot_ = InterlockedIncrement(&tls_max_) - 1;
if (slot_ >= kThreadLocalStorageSize) {
NOTREACHED();
return false;
}
// Setup our destructor.
tls_destructors_[slot_] = destructor;
initialized_ = true;
return true;
}
void ThreadLocalStorage::Slot::Free() {
// At this time, we don't reclaim old indices for TLS slots.
// So all we need to do is wipe the destructor.
tls_destructors_[slot_] = NULL;
initialized_ = false;
}
void* ThreadLocalStorage::Slot::Get() const {
void** tls_data = static_cast<void**>(TlsGetValue(tls_key_));
if (!tls_data)
tls_data = ThreadLocalStorage::Initialize();
DCHECK(slot_ >= 0 && slot_ < kThreadLocalStorageSize);
return tls_data[slot_];
}
void ThreadLocalStorage::Slot::Set(void* value) {
void** tls_data = static_cast<void**>(TlsGetValue(tls_key_));
if (!tls_data)
tls_data = ThreadLocalStorage::Initialize();
DCHECK(slot_ >= 0 && slot_ < kThreadLocalStorageSize);
tls_data[slot_] = value;
}
void ThreadLocalStorage::ThreadExit() {
if (tls_key_ == TLS_OUT_OF_INDEXES)
return;
void** tls_data = static_cast<void**>(TlsGetValue(tls_key_));
// Maybe we have never initialized TLS for this thread.
if (!tls_data)
return;
for (int slot = 0; slot < tls_max_; slot++) {
if (tls_destructors_[slot] != NULL) {
void* value = tls_data[slot];
tls_destructors_[slot](value);
}
}
delete[] tls_data;
// In case there are other "onexit" handlers...
TlsSetValue(tls_key_, NULL);
}
// Thread Termination Callbacks.
// Windows doesn't support a per-thread destructor with its
// TLS primitives. So, we build it manually by inserting a
// function to be called on each thread's exit.
// This magic is from http://www.codeproject.com/threads/tls.asp
// and it works for VC++ 7.0 and later.
#ifdef _WIN64
// This makes the linker create the TLS directory if it's not already
// there. (e.g. if __declspec(thread) is not used).
#pragma comment(linker, "/INCLUDE:_tls_used")
#else // _WIN64
// This makes the linker create the TLS directory if it's not already
// there. (e.g. if __declspec(thread) is not used).
#pragma comment(linker, "/INCLUDE:__tls_used")
#endif // _WIN64
// Static callback function to call with each thread termination.
void NTAPI OnThreadExit(PVOID module, DWORD reason, PVOID reserved)
{
// On XP SP0 & SP1, the DLL_PROCESS_ATTACH is never seen. It is sent on SP2+
// and on W2K and W2K3. So don't assume it is sent.
if (DLL_THREAD_DETACH == reason || DLL_PROCESS_DETACH == reason)
ThreadLocalStorage::ThreadExit();
}