Commit d6dd4b47 authored by Yedidya Feldblum's avatar Yedidya Feldblum Committed by Facebook Github Bot

Use C++ thread_local for fast-path cache in SingletonThreadLocal

Summary:
[Folly] Use C++ `thread_local` for fast-path cache in `SingletonThreadLocal`.

Because modules might be `dlclose`'d, use `static thread_local` rather than `static __thread` in the slow path which integrates the dtor into both `dlclose` and thread exit.

Because there might be multiple dynamically-loaded modules where the `static __thread` caches cannot be merged into a single cache, let all caches be tracked in a list. We have not found a case where this can happen while at the same time the `StaticSingletonManager` singleton does get merged, but this is at least not strictly wrong.

Reviewed By: djwatson

Differential Revision: D7316155

fbshipit-source-id: 0bf06638853ed70464856853349445d9fa6eabc0
parent 3ce3a0c3
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
#pragma once #pragma once
#include <boost/intrusive/list.hpp>
#include <folly/ThreadLocal.h> #include <folly/ThreadLocal.h>
#include <folly/detail/Singleton.h> #include <folly/detail/Singleton.h>
#include <folly/functional/Invoke.h> #include <folly/functional/Invoke.h>
...@@ -60,6 +62,33 @@ template < ...@@ -60,6 +62,33 @@ template <
typename Make = detail::DefaultMake<T>> typename Make = detail::DefaultMake<T>>
class SingletonThreadLocal { class SingletonThreadLocal {
private: private:
struct Wrapper;
using NodeBase = boost::intrusive::list_base_hook<
boost::intrusive::link_mode<boost::intrusive::auto_unlink>>;
struct Node : NodeBase {
Wrapper*& cache;
bool& stale;
Node(Wrapper*& cache_, bool& stale_) : cache(cache_), stale(stale_) {
auto& wrapper = getWrapper();
wrapper.caches.push_front(*this);
cache = &wrapper;
}
~Node() {
clear();
}
void clear() {
cache = nullptr;
stale = true;
}
};
using List =
boost::intrusive::list<Node, boost::intrusive::constant_time_size<false>>;
struct Wrapper { struct Wrapper {
template <typename S> template <typename S>
using MakeRet = is_invocable_r<S, Make>; using MakeRet = is_invocable_r<S, Make>;
...@@ -69,7 +98,7 @@ class SingletonThreadLocal { ...@@ -69,7 +98,7 @@ class SingletonThreadLocal {
alignas(alignof(T)) unsigned char storage[sizeof(T)]; alignas(alignof(T)) unsigned char storage[sizeof(T)];
T object; T object;
}; };
Wrapper** cache{}; List caches;
/* implicit */ operator T&() { /* implicit */ operator T&() {
return object; return object;
...@@ -86,9 +115,10 @@ class SingletonThreadLocal { ...@@ -86,9 +115,10 @@ class SingletonThreadLocal {
(void)Make{}(storage); (void)Make{}(storage);
} }
~Wrapper() { ~Wrapper() {
if (cache) { for (auto& node : caches) {
*cache = nullptr; node.clear();
} }
caches.clear();
object.~T(); object.~T();
} }
}; };
...@@ -100,21 +130,24 @@ class SingletonThreadLocal { ...@@ -100,21 +130,24 @@ class SingletonThreadLocal {
return entry; return entry;
} }
FOLLY_EXPORT FOLLY_NOINLINE static Wrapper& getWrapper() { FOLLY_NOINLINE static Wrapper& getWrapper() {
return *getWrapperTL(); return *getWrapperTL();
} }
FOLLY_ALWAYS_INLINE static Wrapper& getSlow(Wrapper*& cache) { #ifdef FOLLY_TLS
cache = &getWrapper(); FOLLY_NOINLINE static T& getSlow(Wrapper*& cache) {
cache->cache = &cache; static thread_local Wrapper** check = &cache;
return *cache; CHECK_EQ(check, &cache) << "inline function static thread_local merging";
static thread_local bool stale;
static thread_local Node node(cache, stale);
return !stale && node.cache ? *node.cache : getWrapper();
} }
#endif
public: public:
FOLLY_EXPORT FOLLY_ALWAYS_INLINE static T& get() { FOLLY_EXPORT FOLLY_ALWAYS_INLINE static T& get() {
// the absolute minimal conditional-compilation
#ifdef FOLLY_TLS #ifdef FOLLY_TLS
static FOLLY_TLS Wrapper* cache; static thread_local Wrapper* cache;
return FOLLY_LIKELY(!!cache) ? *cache : getSlow(cache); return FOLLY_LIKELY(!!cache) ? *cache : getSlow(cache);
#else #else
return getWrapper(); return getWrapper();
......
...@@ -97,3 +97,21 @@ TEST(SingletonThreadLocalTest, NotMoveConstructibleMake) { ...@@ -97,3 +97,21 @@ TEST(SingletonThreadLocalTest, NotMoveConstructibleMake) {
auto& single = SingletonThreadLocal<Foo, Tag, Make>::get(); auto& single = SingletonThreadLocal<Foo, Tag, Make>::get();
EXPECT_EQ(4, single.b); EXPECT_EQ(4, single.b);
} }
TEST(SingletonThreadLocalTest, AccessAfterFastPathDestruction) {
static std::atomic<int> counter{};
struct Foo {
int i = 3;
};
struct Bar {
~Bar() {
counter += SingletonThreadLocal<Foo>::get().i;
}
};
auto th = std::thread([] {
SingletonThreadLocal<Bar>::get();
counter += SingletonThreadLocal<Foo>::get().i;
});
th.join();
EXPECT_EQ(6, counter);
}
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