Reverted commit D3755446
[folly.git] / folly / detail / ThreadLocalDetail.cpp
1 /*
2  * Copyright 2016 Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include <folly/ThreadLocal.h>
17
18 #include <list>
19 #include <mutex>
20
21 namespace folly { namespace threadlocal_detail {
22
23 StaticMetaBase::StaticMetaBase(ThreadEntry* (*threadEntry)())
24     : nextId_(1), threadEntry_(threadEntry) {
25   head_.next = head_.prev = &head_;
26   int ret = pthread_key_create(&pthreadKey_, &onThreadExit);
27   checkPosixError(ret, "pthread_key_create failed");
28   PthreadKeyUnregister::registerKey(pthreadKey_);
29 }
30
31 void StaticMetaBase::onThreadExit(void* ptr) {
32 #ifdef FOLLY_TLD_USE_FOLLY_TLS
33   auto threadEntry = static_cast<ThreadEntry*>(ptr);
34 #else
35   std::unique_ptr<ThreadEntry> threadEntry(static_cast<ThreadEntry*>(ptr));
36 #endif
37   DCHECK_GT(threadEntry->elementsCapacity, 0);
38   auto& meta = *threadEntry->meta;
39
40   // Make sure this ThreadEntry is available if ThreadLocal A is accessed in
41   // ThreadLocal B destructor.
42   pthread_setspecific(meta.pthreadKey_, &(*threadEntry));
43   SCOPE_EXIT {
44     pthread_setspecific(meta.pthreadKey_, nullptr);
45   };
46
47   {
48     std::lock_guard<std::mutex> g(meta.lock_);
49     meta.erase(&(*threadEntry));
50     // No need to hold the lock any longer; the ThreadEntry is private to this
51     // thread now that it's been removed from meta.
52   }
53   // NOTE: User-provided deleter / object dtor itself may be using ThreadLocal
54   // with the same Tag, so dispose() calls below may (re)create some of the
55   // elements or even increase elementsCapacity, thus multiple cleanup rounds
56   // may be required.
57   for (bool shouldRun = true; shouldRun;) {
58     shouldRun = false;
59     FOR_EACH_RANGE (i, 0, threadEntry->elementsCapacity) {
60       if (threadEntry->elements[i].dispose(TLPDestructionMode::THIS_THREAD)) {
61         shouldRun = true;
62       }
63     }
64   }
65   free(threadEntry->elements);
66   threadEntry->elements = nullptr;
67   threadEntry->meta = nullptr;
68 }
69
70 uint32_t StaticMetaBase::allocate(EntryID* ent) {
71   uint32_t id;
72   auto& meta = *this;
73   std::lock_guard<std::mutex> g(meta.lock_);
74
75   id = ent->value.load();
76   if (id != kEntryIDInvalid) {
77     return id;
78   }
79
80   if (!meta.freeIds_.empty()) {
81     id = meta.freeIds_.back();
82     meta.freeIds_.pop_back();
83   } else {
84     id = meta.nextId_++;
85   }
86
87   uint32_t old_id = ent->value.exchange(id);
88   DCHECK_EQ(old_id, kEntryIDInvalid);
89   return id;
90 }
91
92 void StaticMetaBase::destroy(EntryID* ent) {
93   try {
94     auto& meta = *this;
95     // Elements in other threads that use this id.
96     std::vector<ElementWrapper> elements;
97     {
98       std::lock_guard<std::mutex> g(meta.lock_);
99       uint32_t id = ent->value.exchange(kEntryIDInvalid);
100       if (id == kEntryIDInvalid) {
101         return;
102       }
103
104       for (ThreadEntry* e = meta.head_.next; e != &meta.head_; e = e->next) {
105         if (id < e->elementsCapacity && e->elements[id].ptr) {
106           elements.push_back(e->elements[id]);
107
108           /*
109            * Writing another thread's ThreadEntry from here is fine;
110            * the only other potential reader is the owning thread --
111            * from onThreadExit (which grabs the lock, so is properly
112            * synchronized with us) or from get(), which also grabs
113            * the lock if it needs to resize the elements vector.
114            *
115            * We can't conflict with reads for a get(id), because
116            * it's illegal to call get on a thread local that's
117            * destructing.
118            */
119           e->elements[id].ptr = nullptr;
120           e->elements[id].deleter1 = nullptr;
121           e->elements[id].ownsDeleter = false;
122         }
123       }
124       meta.freeIds_.push_back(id);
125     }
126     // Delete elements outside the lock
127     for (ElementWrapper& elem : elements) {
128       elem.dispose(TLPDestructionMode::ALL_THREADS);
129     }
130   } catch (...) { // Just in case we get a lock error or something anyway...
131     LOG(WARNING) << "Destructor discarding an exception that was thrown.";
132   }
133 }
134
135 /**
136  * Reserve enough space in the ThreadEntry::elements for the item
137  * @id to fit in.
138  */
139 void StaticMetaBase::reserve(EntryID* id) {
140   auto& meta = *this;
141   ThreadEntry* threadEntry = (*threadEntry_)();
142   size_t prevCapacity = threadEntry->elementsCapacity;
143
144   uint32_t idval = id->getOrAllocate(meta);
145   if (prevCapacity > idval) {
146     return;
147   }
148   // Growth factor < 2, see folly/docs/FBVector.md; + 5 to prevent
149   // very slow start.
150   size_t newCapacity = static_cast<size_t>((idval + 5) * 1.7);
151   assert(newCapacity > prevCapacity);
152   ElementWrapper* reallocated = nullptr;
153
154   // Need to grow. Note that we can't call realloc, as elements is
155   // still linked in meta, so another thread might access invalid memory
156   // after realloc succeeds. We'll copy by hand and update our ThreadEntry
157   // under the lock.
158   if (usingJEMalloc()) {
159     bool success = false;
160     size_t newByteSize = nallocx(newCapacity * sizeof(ElementWrapper), 0);
161
162     // Try to grow in place.
163     //
164     // Note that xallocx(MALLOCX_ZERO) will only zero newly allocated memory,
165     // even if a previous allocation allocated more than we requested.
166     // This is fine; we always use MALLOCX_ZERO with jemalloc and we
167     // always expand our allocation to the real size.
168     if (prevCapacity * sizeof(ElementWrapper) >= jemallocMinInPlaceExpandable) {
169       success =
170           (xallocx(threadEntry->elements, newByteSize, 0, MALLOCX_ZERO) ==
171            newByteSize);
172     }
173
174     // In-place growth failed.
175     if (!success) {
176       success =
177           ((reallocated = static_cast<ElementWrapper*>(
178                 mallocx(newByteSize, MALLOCX_ZERO))) != nullptr);
179     }
180
181     if (success) {
182       // Expand to real size
183       assert(newByteSize / sizeof(ElementWrapper) >= newCapacity);
184       newCapacity = newByteSize / sizeof(ElementWrapper);
185     } else {
186       throw std::bad_alloc();
187     }
188   } else { // no jemalloc
189     // calloc() is simpler than malloc() followed by memset(), and
190     // potentially faster when dealing with a lot of memory, as it can get
191     // already-zeroed pages from the kernel.
192     reallocated = static_cast<ElementWrapper*>(
193         calloc(newCapacity, sizeof(ElementWrapper)));
194     if (!reallocated) {
195       throw std::bad_alloc();
196     }
197   }
198
199   // Success, update the entry
200   {
201     std::lock_guard<std::mutex> g(meta.lock_);
202
203     if (prevCapacity == 0) {
204       meta.push_back(threadEntry);
205     }
206
207     if (reallocated) {
208       /*
209        * Note: we need to hold the meta lock when copying data out of
210        * the old vector, because some other thread might be
211        * destructing a ThreadLocal and writing to the elements vector
212        * of this thread.
213        */
214       if (prevCapacity != 0) {
215         memcpy(
216             reallocated,
217             threadEntry->elements,
218             sizeof(*reallocated) * prevCapacity);
219       }
220       std::swap(reallocated, threadEntry->elements);
221     }
222     threadEntry->elementsCapacity = newCapacity;
223   }
224
225   free(reallocated);
226 }
227
228 namespace {
229
230 struct AtForkTask {
231   folly::Function<void()> prepare;
232   folly::Function<void()> parent;
233   folly::Function<void()> child;
234 };
235
236 class AtForkList {
237  public:
238   static AtForkList& instance() {
239     static auto instance = new AtForkList();
240     return *instance;
241   }
242
243   static void prepare() noexcept {
244     instance().tasksLock.lock();
245     auto& tasks = instance().tasks;
246     for (auto task = tasks.rbegin(); task != tasks.rend(); ++task) {
247       task->prepare();
248     }
249   }
250
251   static void parent() noexcept {
252     auto& tasks = instance().tasks;
253     for (auto& task : tasks) {
254       task.parent();
255     }
256     instance().tasksLock.unlock();
257   }
258
259   static void child() noexcept {
260     auto& tasks = instance().tasks;
261     for (auto& task : tasks) {
262       task.child();
263     }
264     instance().tasksLock.unlock();
265   }
266
267   std::mutex tasksLock;
268   std::list<AtForkTask> tasks;
269
270  private:
271   AtForkList() {
272 #if FOLLY_HAVE_PTHREAD_ATFORK
273     int ret = pthread_atfork(
274         &AtForkList::prepare, &AtForkList::parent, &AtForkList::child);
275     checkPosixError(ret, "pthread_atfork failed");
276 #elif !__ANDROID__ && !defined(_MSC_VER)
277 // pthread_atfork is not part of the Android NDK at least as of n9d. If
278 // something is trying to call native fork() directly at all with Android's
279 // process management model, this is probably the least of the problems.
280 //
281 // But otherwise, this is a problem.
282 #warning pthread_atfork unavailable
283 #endif
284   }
285 };
286 }
287
288 void StaticMetaBase::initAtFork() {
289   AtForkList::instance();
290 }
291
292 void StaticMetaBase::registerAtFork(
293     folly::Function<void()> prepare,
294     folly::Function<void()> parent,
295     folly::Function<void()> child) {
296   std::lock_guard<std::mutex> lg(AtForkList::instance().tasksLock);
297   AtForkList::instance().tasks.push_back(
298       {std::move(prepare), std::move(parent), std::move(child)});
299 }
300
301 FOLLY_STATIC_CTOR_PRIORITY_MAX
302 PthreadKeyUnregister PthreadKeyUnregister::instance_;
303 }}