9e7fda6828716589bd2507caeff6359a0493778b
[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 namespace folly { namespace threadlocal_detail {
19
20 StaticMetaBase::StaticMetaBase(ThreadEntry* (*threadEntry)())
21     : nextId_(1), threadEntry_(threadEntry) {
22   head_.next = head_.prev = &head_;
23   int ret = pthread_key_create(&pthreadKey_, &onThreadExit);
24   checkPosixError(ret, "pthread_key_create failed");
25   PthreadKeyUnregister::registerKey(pthreadKey_);
26 }
27
28 void StaticMetaBase::onThreadExit(void* ptr) {
29 #ifdef FOLLY_TLD_USE_FOLLY_TLS
30   auto threadEntry = static_cast<ThreadEntry*>(ptr);
31 #else
32   std::unique_ptr<ThreadEntry> threadEntry(static_cast<ThreadEntry*>(ptr));
33 #endif
34   DCHECK_GT(threadEntry->elementsCapacity, 0);
35   auto& meta = *threadEntry->meta;
36
37   // Make sure this ThreadEntry is available if ThreadLocal A is accessed in
38   // ThreadLocal B destructor.
39   pthread_setspecific(meta.pthreadKey_, threadEntry);
40   SCOPE_EXIT {
41     pthread_setspecific(meta.pthreadKey_, nullptr);
42   };
43
44   {
45     std::lock_guard<std::mutex> g(meta.lock_);
46     meta.erase(&(*threadEntry));
47     // No need to hold the lock any longer; the ThreadEntry is private to this
48     // thread now that it's been removed from meta.
49   }
50   // NOTE: User-provided deleter / object dtor itself may be using ThreadLocal
51   // with the same Tag, so dispose() calls below may (re)create some of the
52   // elements or even increase elementsCapacity, thus multiple cleanup rounds
53   // may be required.
54   for (bool shouldRun = true; shouldRun;) {
55     shouldRun = false;
56     FOR_EACH_RANGE (i, 0, threadEntry->elementsCapacity) {
57       if (threadEntry->elements[i].dispose(TLPDestructionMode::THIS_THREAD)) {
58         shouldRun = true;
59       }
60     }
61   }
62   free(threadEntry->elements);
63   threadEntry->elements = nullptr;
64   threadEntry->meta = nullptr;
65 }
66
67 uint32_t StaticMetaBase::allocate(EntryID* ent) {
68   uint32_t id;
69   auto& meta = *this;
70   std::lock_guard<std::mutex> g(meta.lock_);
71
72   id = ent->value.load();
73   if (id != kEntryIDInvalid) {
74     return id;
75   }
76
77   if (!meta.freeIds_.empty()) {
78     id = meta.freeIds_.back();
79     meta.freeIds_.pop_back();
80   } else {
81     id = meta.nextId_++;
82   }
83
84   uint32_t old_id = ent->value.exchange(id);
85   DCHECK_EQ(old_id, kEntryIDInvalid);
86   return id;
87 }
88
89 void StaticMetaBase::destroy(EntryID* ent) {
90   try {
91     auto& meta = *this;
92     // Elements in other threads that use this id.
93     std::vector<ElementWrapper> elements;
94     {
95       std::lock_guard<std::mutex> g(meta.lock_);
96       uint32_t id = ent->value.exchange(kEntryIDInvalid);
97       if (id == kEntryIDInvalid) {
98         return;
99       }
100
101       for (ThreadEntry* e = meta.head_.next; e != &meta.head_; e = e->next) {
102         if (id < e->elementsCapacity && e->elements[id].ptr) {
103           elements.push_back(e->elements[id]);
104
105           /*
106            * Writing another thread's ThreadEntry from here is fine;
107            * the only other potential reader is the owning thread --
108            * from onThreadExit (which grabs the lock, so is properly
109            * synchronized with us) or from get(), which also grabs
110            * the lock if it needs to resize the elements vector.
111            *
112            * We can't conflict with reads for a get(id), because
113            * it's illegal to call get on a thread local that's
114            * destructing.
115            */
116           e->elements[id].ptr = nullptr;
117           e->elements[id].deleter1 = nullptr;
118           e->elements[id].ownsDeleter = false;
119         }
120       }
121       meta.freeIds_.push_back(id);
122     }
123     // Delete elements outside the lock
124     for (ElementWrapper& elem : elements) {
125       elem.dispose(TLPDestructionMode::ALL_THREADS);
126     }
127   } catch (...) { // Just in case we get a lock error or something anyway...
128     LOG(WARNING) << "Destructor discarding an exception that was thrown.";
129   }
130 }
131
132 /**
133  * Reserve enough space in the ThreadEntry::elements for the item
134  * @id to fit in.
135  */
136 void StaticMetaBase::reserve(EntryID* id) {
137   auto& meta = *this;
138   ThreadEntry* threadEntry = (*threadEntry_)();
139   size_t prevCapacity = threadEntry->elementsCapacity;
140
141   uint32_t idval = id->getOrAllocate(meta);
142   if (prevCapacity > idval) {
143     return;
144   }
145   // Growth factor < 2, see folly/docs/FBVector.md; + 5 to prevent
146   // very slow start.
147   size_t newCapacity = static_cast<size_t>((idval + 5) * 1.7);
148   assert(newCapacity > prevCapacity);
149   ElementWrapper* reallocated = nullptr;
150
151   // Need to grow. Note that we can't call realloc, as elements is
152   // still linked in meta, so another thread might access invalid memory
153   // after realloc succeeds. We'll copy by hand and update our ThreadEntry
154   // under the lock.
155   if (usingJEMalloc()) {
156     bool success = false;
157     size_t newByteSize = nallocx(newCapacity * sizeof(ElementWrapper), 0);
158
159     // Try to grow in place.
160     //
161     // Note that xallocx(MALLOCX_ZERO) will only zero newly allocated memory,
162     // even if a previous allocation allocated more than we requested.
163     // This is fine; we always use MALLOCX_ZERO with jemalloc and we
164     // always expand our allocation to the real size.
165     if (prevCapacity * sizeof(ElementWrapper) >= jemallocMinInPlaceExpandable) {
166       success =
167           (xallocx(threadEntry->elements, newByteSize, 0, MALLOCX_ZERO) ==
168            newByteSize);
169     }
170
171     // In-place growth failed.
172     if (!success) {
173       success =
174           ((reallocated = static_cast<ElementWrapper*>(
175                 mallocx(newByteSize, MALLOCX_ZERO))) != nullptr);
176     }
177
178     if (success) {
179       // Expand to real size
180       assert(newByteSize / sizeof(ElementWrapper) >= newCapacity);
181       newCapacity = newByteSize / sizeof(ElementWrapper);
182     } else {
183       throw std::bad_alloc();
184     }
185   } else { // no jemalloc
186     // calloc() is simpler than malloc() followed by memset(), and
187     // potentially faster when dealing with a lot of memory, as it can get
188     // already-zeroed pages from the kernel.
189     reallocated = static_cast<ElementWrapper*>(
190         calloc(newCapacity, sizeof(ElementWrapper)));
191     if (!reallocated) {
192       throw std::bad_alloc();
193     }
194   }
195
196   // Success, update the entry
197   {
198     std::lock_guard<std::mutex> g(meta.lock_);
199
200     if (prevCapacity == 0) {
201       meta.push_back(threadEntry);
202     }
203
204     if (reallocated) {
205       /*
206        * Note: we need to hold the meta lock when copying data out of
207        * the old vector, because some other thread might be
208        * destructing a ThreadLocal and writing to the elements vector
209        * of this thread.
210        */
211       if (prevCapacity != 0) {
212         memcpy(
213             reallocated,
214             threadEntry->elements,
215             sizeof(*reallocated) * prevCapacity);
216       }
217       std::swap(reallocated, threadEntry->elements);
218     }
219     threadEntry->elementsCapacity = newCapacity;
220   }
221
222   free(reallocated);
223 }
224
225 FOLLY_STATIC_CTOR_PRIORITY_MAX
226 PthreadKeyUnregister PthreadKeyUnregister::instance_;
227 }}