Commit ea10c904 authored by Peter Griess's avatar Peter Griess

Handle platforms that don't support __thread.

Summary:
- Apple platforms either have different __thread behavior than Linux: on
i386 __thread values are zeroed out before the destructor provided to
pthread_key_create(3) is called; on ARM, __thread isn't supported at
all. To handle this, use pthread_getspecific(3) to manage the array of
IDs on these platforms only.

Test Plan:
- fbconfig -r folly && fbmake runtests
- ./configure && make check on Ubuntu/FC/Mac

Reviewed By: simpkins@fb.com

FB internal diff: D1008923
parent a0fe3c18
...@@ -124,6 +124,11 @@ class ThreadLocal { ...@@ -124,6 +124,11 @@ class ThreadLocal {
* We use a single global pthread_key_t per Tag to manage object destruction and * We use a single global pthread_key_t per Tag to manage object destruction and
* memory cleanup upon thread exit because there is a finite number of * memory cleanup upon thread exit because there is a finite number of
* pthread_key_t's available per machine. * pthread_key_t's available per machine.
*
* NOTE: Apple platforms don't support the same semantics for __thread that
* Linux does (and it's only supported at all on i386). For these, use
* pthread_setspecific()/pthread_getspecific() for the per-thread
* storage.
*/ */
template<class T, class Tag=void> template<class T, class Tag=void>
......
...@@ -168,7 +168,9 @@ struct StaticMeta { ...@@ -168,7 +168,9 @@ struct StaticMeta {
t->next = t->prev = t; t->next = t->prev = t;
} }
#if !__APPLE__
static __thread ThreadEntry threadEntry_; static __thread ThreadEntry threadEntry_;
#endif
static StaticMeta<Tag>* inst_; static StaticMeta<Tag>* inst_;
StaticMeta() : nextId_(1) { StaticMeta() : nextId_(1) {
...@@ -185,6 +187,21 @@ struct StaticMeta { ...@@ -185,6 +187,21 @@ struct StaticMeta {
LOG(FATAL) << "StaticMeta lives forever!"; LOG(FATAL) << "StaticMeta lives forever!";
} }
static ThreadEntry* getThreadEntry() {
#if !__APPLE__
return &threadEntry_;
#else
ThreadEntry* threadEntry =
static_cast<ThreadEntry*>(pthread_getspecific(inst_->pthreadKey_));
if (!threadEntry) {
threadEntry = new ThreadEntry();
int ret = pthread_setspecific(inst_->pthreadKey_, threadEntry);
checkPosixError(ret, "pthread_setspecific failed");
}
return threadEntry;
#endif
}
static void preFork(void) { static void preFork(void) {
instance().lock_.lock(); // Make sure it's created instance().lock_.lock(); // Make sure it's created
} }
...@@ -194,28 +211,39 @@ struct StaticMeta { ...@@ -194,28 +211,39 @@ struct StaticMeta {
} }
static void onForkChild(void) { static void onForkChild(void) {
// only the current thread survives
inst_->head_.next = inst_->head_.prev = &inst_->head_; inst_->head_.next = inst_->head_.prev = &inst_->head_;
inst_->push_back(&threadEntry_); // only the current thread survives inst_->push_back(getThreadEntry());
inst_->lock_.unlock(); inst_->lock_.unlock();
} }
static void onThreadExit(void* ptr) { static void onThreadExit(void* ptr) {
auto & meta = instance(); auto & meta = instance();
#if !__APPLE__
ThreadEntry* threadEntry = getThreadEntry();
DCHECK_EQ(ptr, &meta); DCHECK_EQ(ptr, &meta);
// We wouldn't call pthread_setspecific unless we actually called get() DCHECK_GT(threadEntry->elementsCapacity, 0);
DCHECK_NE(threadEntry_.elementsCapacity, 0); #else
ThreadEntry* threadEntry = static_cast<ThreadEntry*>(ptr);
#endif
{ {
std::lock_guard<std::mutex> g(meta.lock_); std::lock_guard<std::mutex> g(meta.lock_);
meta.erase(&threadEntry_); meta.erase(threadEntry);
// No need to hold the lock any longer; threadEntry_ is private to this // No need to hold the lock any longer; the ThreadEntry is private to this
// thread now that it's been removed from meta. // thread now that it's been removed from meta.
} }
FOR_EACH_RANGE(i, 0, threadEntry_.elementsCapacity) { FOR_EACH_RANGE(i, 0, threadEntry->elementsCapacity) {
threadEntry_.elements[i].dispose(TLPDestructionMode::THIS_THREAD); threadEntry->elements[i].dispose(TLPDestructionMode::THIS_THREAD);
} }
free(threadEntry_.elements); free(threadEntry->elements);
threadEntry_.elements = NULL; threadEntry->elements = NULL;
pthread_setspecific(meta.pthreadKey_, NULL); pthread_setspecific(meta.pthreadKey_, NULL);
#if __APPLE__
// Allocated in getThreadEntry(); free it
delete threadEntry;
#endif
} }
static int create() { static int create() {
...@@ -270,21 +298,22 @@ struct StaticMeta { ...@@ -270,21 +298,22 @@ struct StaticMeta {
} }
/** /**
* Reserve enough space in the threadEntry_.elements for the item * Reserve enough space in the ThreadEntry::elements for the item
* @id to fit in. * @id to fit in.
*/ */
static void reserve(int id) { static void reserve(int id) {
size_t prevCapacity = threadEntry_.elementsCapacity; auto& meta = instance();
ThreadEntry* threadEntry = getThreadEntry();
size_t prevCapacity = threadEntry->elementsCapacity;
// Growth factor < 2, see folly/docs/FBVector.md; + 5 to prevent // Growth factor < 2, see folly/docs/FBVector.md; + 5 to prevent
// very slow start. // very slow start.
size_t newCapacity = static_cast<size_t>((id + 5) * 1.7); size_t newCapacity = static_cast<size_t>((id + 5) * 1.7);
assert(newCapacity > prevCapacity); assert(newCapacity > prevCapacity);
auto& meta = instance();
ElementWrapper* reallocated = nullptr; ElementWrapper* reallocated = nullptr;
// Need to grow. Note that we can't call realloc, as elements is // Need to grow. Note that we can't call realloc, as elements is
// still linked in meta, so another thread might access invalid memory // still linked in meta, so another thread might access invalid memory
// after realloc succeeds. We'll copy by hand and update threadEntry_ // after realloc succeeds. We'll copy by hand and update our ThreadEntry
// under the lock. // under the lock.
if (usingJEMalloc()) { if (usingJEMalloc()) {
bool success = false; bool success = false;
...@@ -299,7 +328,7 @@ struct StaticMeta { ...@@ -299,7 +328,7 @@ struct StaticMeta {
// always expand our allocation to the real size. // always expand our allocation to the real size.
if (prevCapacity * sizeof(ElementWrapper) >= if (prevCapacity * sizeof(ElementWrapper) >=
jemallocMinInPlaceExpandable) { jemallocMinInPlaceExpandable) {
success = (rallocm(reinterpret_cast<void**>(&threadEntry_.elements), success = (rallocm(reinterpret_cast<void**>(&threadEntry->elements),
&realByteSize, &realByteSize,
newByteSize, newByteSize,
0, 0,
...@@ -341,7 +370,7 @@ struct StaticMeta { ...@@ -341,7 +370,7 @@ struct StaticMeta {
std::lock_guard<std::mutex> g(meta.lock_); std::lock_guard<std::mutex> g(meta.lock_);
if (prevCapacity == 0) { if (prevCapacity == 0) {
meta.push_back(&threadEntry_); meta.push_back(threadEntry);
} }
if (reallocated) { if (reallocated) {
...@@ -351,31 +380,36 @@ struct StaticMeta { ...@@ -351,31 +380,36 @@ struct StaticMeta {
* destructing a ThreadLocal and writing to the elements vector * destructing a ThreadLocal and writing to the elements vector
* of this thread. * of this thread.
*/ */
memcpy(reallocated, threadEntry_.elements, memcpy(reallocated, threadEntry->elements,
sizeof(ElementWrapper) * prevCapacity); sizeof(ElementWrapper) * prevCapacity);
using std::swap; using std::swap;
swap(reallocated, threadEntry_.elements); swap(reallocated, threadEntry->elements);
} }
threadEntry_.elementsCapacity = newCapacity; threadEntry->elementsCapacity = newCapacity;
} }
free(reallocated); free(reallocated);
#if !__APPLE__
if (prevCapacity == 0) { if (prevCapacity == 0) {
pthread_setspecific(meta.pthreadKey_, &meta); pthread_setspecific(meta.pthreadKey_, &meta);
} }
#endif
} }
static ElementWrapper& get(int id) { static ElementWrapper& get(int id) {
if (UNLIKELY(threadEntry_.elementsCapacity <= id)) { ThreadEntry* threadEntry = getThreadEntry();
if (UNLIKELY(threadEntry->elementsCapacity <= id)) {
reserve(id); reserve(id);
assert(threadEntry_.elementsCapacity > id); assert(threadEntry->elementsCapacity > id);
} }
return threadEntry_.elements[id]; return threadEntry->elements[id];
} }
}; };
#if !__APPLE__
template <class Tag> __thread ThreadEntry StaticMeta<Tag>::threadEntry_ = {0}; template <class Tag> __thread ThreadEntry StaticMeta<Tag>::threadEntry_ = {0};
#endif
template <class Tag> StaticMeta<Tag>* StaticMeta<Tag>::inst_ = nullptr; template <class Tag> StaticMeta<Tag>* StaticMeta<Tag>::inst_ = nullptr;
} // namespace threadlocal_detail } // namespace threadlocal_detail
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment