#include <glog/logging.h>
+#include "folly/Exception.h"
#include "folly/Foreach.h"
#include "folly/Malloc.h"
static StaticMeta<Tag>& instance() {
// Leak it on exit, there's only one per process and we don't have to
// worry about synchronization with exiting threads.
- static bool constructed = (inst = new StaticMeta<Tag>());
+ static bool constructed = (inst_ = new StaticMeta<Tag>());
(void)constructed; // suppress unused warning
- return *inst;
+ return *inst_;
}
int nextId_;
}
static __thread ThreadEntry threadEntry_;
- static StaticMeta<Tag>* inst;
+ static StaticMeta<Tag>* inst_;
StaticMeta() : nextId_(1) {
head_.next = head_.prev = &head_;
int ret = pthread_key_create(&pthreadKey_, &onThreadExit);
- if (ret != 0) {
- std::string msg;
- switch (ret) {
- case EAGAIN:
- char buf[100];
- snprintf(buf, sizeof(buf), "PTHREAD_KEYS_MAX (%d) is exceeded",
- PTHREAD_KEYS_MAX);
- msg = buf;
- break;
- case ENOMEM:
- msg = "Out-of-memory";
- break;
- default:
- msg = "(unknown error)";
- }
- throw std::runtime_error("pthread_key_create failed: " + msg);
- }
+ checkPosixError(ret, "pthread_key_create failed");
+
+ ret = pthread_atfork(/*prepare*/ &StaticMeta::preFork,
+ /*parent*/ &StaticMeta::onForkParent,
+ /*child*/ &StaticMeta::onForkChild);
+ checkPosixError(ret, "pthread_atfork failed");
}
~StaticMeta() {
LOG(FATAL) << "StaticMeta lives forever!";
}
+ static void preFork(void) {
+ instance().lock_.lock(); // Make sure it's created
+ }
+
+ static void onForkParent(void) {
+ inst_->lock_.unlock();
+ }
+
+ static void onForkChild(void) {
+ inst_->head_.next = inst_->head_.prev = &inst_->head_;
+ inst_->push_back(&threadEntry_); // only the current thread survives
+ inst_->lock_.unlock();
+ }
+
static void onThreadExit(void* ptr) {
auto & meta = instance();
DCHECK_EQ(ptr, &meta);
};
template <class Tag> __thread ThreadEntry StaticMeta<Tag>::threadEntry_ = {0};
-template <class Tag> StaticMeta<Tag>* StaticMeta<Tag>::inst = nullptr;
+template <class Tag> StaticMeta<Tag>* StaticMeta<Tag>::inst_ = nullptr;
} // namespace threadlocal_detail
} // namespace folly
#include "folly/ThreadLocal.h"
+#include <sys/types.h>
+#include <sys/wait.h>
#include <map>
#include <unordered_map>
#include <set>
#include <mutex>
#include <condition_variable>
#include <thread>
+#include <unistd.h>
#include <boost/thread/tss.hpp>
#include <gtest/gtest.h>
#include <gflags/gflags.h>
EXPECT_EQ(4, tls.size());
}
+// Yes, threads and fork don't mix
+// (http://cppwisdom.quora.com/Why-threads-and-fork-dont-mix) but if you're
+// stupid or desperate enough to try, we shouldn't stand in your way.
+namespace {
+class HoldsOne {
+ public:
+ HoldsOne() : value_(1) { }
+ // Do an actual access to catch the buggy case where this == nullptr
+ int value() const { return value_; }
+ private:
+ int value_;
+};
+
+struct HoldsOneTag {};
+
+ThreadLocal<HoldsOne, HoldsOneTag> ptr;
+
+int totalValue() {
+ int value = 0;
+ for (auto& p : ptr.accessAllThreads()) {
+ value += p.value();
+ }
+ return value;
+}
+
+} // namespace
+
+TEST(ThreadLocal, Fork) {
+ EXPECT_EQ(1, ptr->value()); // ensure created
+ EXPECT_EQ(1, totalValue());
+ // Spawn a new thread
+
+ std::mutex mutex;
+ bool started = false;
+ std::condition_variable startedCond;
+ bool stopped = false;
+ std::condition_variable stoppedCond;
+
+ std::thread t([&] () {
+ EXPECT_EQ(1, ptr->value()); // ensure created
+ {
+ std::unique_lock<std::mutex> lock(mutex);
+ started = true;
+ startedCond.notify_all();
+ }
+ {
+ std::unique_lock<std::mutex> lock(mutex);
+ while (!stopped) {
+ stoppedCond.wait(lock);
+ }
+ }
+ });
+
+ {
+ std::unique_lock<std::mutex> lock(mutex);
+ while (!started) {
+ startedCond.wait(lock);
+ }
+ }
+
+ EXPECT_EQ(2, totalValue());
+
+ pid_t pid = fork();
+ if (pid == 0) {
+ // in child
+ int v = totalValue();
+
+ // exit successfully if v == 1 (one thread)
+ // diagnostic error code otherwise :)
+ switch (v) {
+ case 1: _exit(0);
+ case 0: _exit(1);
+ }
+ _exit(2);
+ } else if (pid > 0) {
+ // in parent
+ int status;
+ EXPECT_EQ(pid, waitpid(pid, &status, 0));
+ EXPECT_TRUE(WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+ } else {
+ EXPECT_TRUE(false) << "fork failed";
+ }
+
+ EXPECT_EQ(2, totalValue());
+
+ {
+ std::unique_lock<std::mutex> lock(mutex);
+ stopped = true;
+ stoppedCond.notify_all();
+ }
+
+ t.join();
+
+ EXPECT_EQ(1, totalValue());
+}
+
// Simple reference implementation using pthread_get_specific
template<typename T>
class PThreadGetSpecific {