strings join
[folly.git] / folly / detail / ThreadLocalDetail.h
1 /*
2  * Copyright 2012 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
17 #ifndef FOLLY_DETAIL_THREADLOCALDETAIL_H_
18 #define FOLLY_DETAIL_THREADLOCALDETAIL_H_
19
20 #include <limits.h>
21 #include <pthread.h>
22 #include <list>
23 #include <string>
24 #include <vector>
25
26 #include <boost/thread/mutex.hpp>
27
28 #include <glog/logging.h>
29
30 #include "folly/Foreach.h"
31 #include "folly/Malloc.h"
32
33 namespace folly {
34 namespace threadlocal_detail {
35
36 /**
37  * Base class for deleters.
38  */
39 class DeleterBase {
40  public:
41   virtual ~DeleterBase() { }
42   virtual void dispose(void* ptr, TLPDestructionMode mode) const = 0;
43 };
44
45 /**
46  * Simple deleter class that calls delete on the passed-in pointer.
47  */
48 template <class Ptr>
49 class SimpleDeleter : public DeleterBase {
50  public:
51   virtual void dispose(void* ptr, TLPDestructionMode mode) const {
52     delete static_cast<Ptr>(ptr);
53   }
54 };
55
56 /**
57  * Custom deleter that calls a given callable.
58  */
59 template <class Ptr, class Deleter>
60 class CustomDeleter : public DeleterBase {
61  public:
62   explicit CustomDeleter(Deleter d) : deleter_(d) { }
63   virtual void dispose(void* ptr, TLPDestructionMode mode) const {
64     deleter_(static_cast<Ptr>(ptr), mode);
65   }
66  private:
67   Deleter deleter_;
68 };
69
70
71 /**
72  * POD wrapper around an element (a void*) and an associated deleter.
73  * This must be POD, as we memset() it to 0 and memcpy() it around.
74  */
75 struct ElementWrapper {
76   void dispose(TLPDestructionMode mode) {
77     if (ptr != NULL) {
78       DCHECK(deleter != NULL);
79       deleter->dispose(ptr, mode);
80       if (ownsDeleter) {
81         delete deleter;
82       }
83       ptr = NULL;
84       deleter = NULL;
85       ownsDeleter = false;
86     }
87   }
88
89   template <class Ptr>
90   void set(Ptr p) {
91     DCHECK(ptr == NULL);
92     DCHECK(deleter == NULL);
93
94     if (p) {
95       // We leak a single object here but that is ok.  If we used an
96       // object directly, there is a chance that the destructor will be
97       // called on that static object before any of the ElementWrappers
98       // are disposed and that isn't so nice.
99       static auto d = new SimpleDeleter<Ptr>();
100       ptr = p;
101       deleter = d;
102       ownsDeleter = false;
103     }
104   }
105
106   template <class Ptr, class Deleter>
107   void set(Ptr p, Deleter d) {
108     DCHECK(ptr == NULL);
109     DCHECK(deleter == NULL);
110     if (p) {
111       ptr = p;
112       deleter = new CustomDeleter<Ptr,Deleter>(d);
113       ownsDeleter = true;
114     }
115   }
116
117   void* ptr;
118   DeleterBase* deleter;
119   bool ownsDeleter;
120 };
121
122 /**
123  * Per-thread entry.  Each thread using a StaticMeta object has one.
124  * This is written from the owning thread only (under the lock), read
125  * from the owning thread (no lock necessary), and read from other threads
126  * (under the lock).
127  */
128 struct ThreadEntry {
129   ElementWrapper* elements;
130   size_t elementsCapacity;
131   ThreadEntry* next;
132   ThreadEntry* prev;
133 };
134
135 // Held in a singleton to track our global instances.
136 // We have one of these per "Tag", by default one for the whole system
137 // (Tag=void).
138 //
139 // Creating and destroying ThreadLocalPtr objects, as well as thread exit
140 // for threads that use ThreadLocalPtr objects collide on a lock inside
141 // StaticMeta; you can specify multiple Tag types to break that lock.
142 template <class Tag>
143 struct StaticMeta {
144   static StaticMeta<Tag>& instance() {
145     // Leak it on exit, there's only one per process and we don't have to
146     // worry about synchronization with exiting threads.
147     static bool constructed = (inst = new StaticMeta<Tag>());
148     return *inst;
149   }
150
151   int nextId_;
152   std::vector<int> freeIds_;
153   boost::mutex lock_;
154   pthread_key_t pthreadKey_;
155   ThreadEntry head_;
156
157   void push_back(ThreadEntry* t) {
158     t->next = &head_;
159     t->prev = head_.prev;
160     head_.prev->next = t;
161     head_.prev = t;
162   }
163
164   void erase(ThreadEntry* t) {
165     t->next->prev = t->prev;
166     t->prev->next = t->next;
167     t->next = t->prev = t;
168   }
169
170   static __thread ThreadEntry threadEntry_;
171   static StaticMeta<Tag>* inst;
172
173   StaticMeta() : nextId_(1) {
174     head_.next = head_.prev = &head_;
175     int ret = pthread_key_create(&pthreadKey_, &onThreadExit);
176     if (ret != 0) {
177       std::string msg;
178       switch (ret) {
179         case EAGAIN:
180           char buf[100];
181           snprintf(buf, sizeof(buf), "PTHREAD_KEYS_MAX (%d) is exceeded",
182                    PTHREAD_KEYS_MAX);
183           msg = buf;
184           break;
185         case ENOMEM:
186           msg = "Out-of-memory";
187           break;
188         default:
189           msg = "(unknown error)";
190       }
191       throw std::runtime_error("pthread_key_create failed: " + msg);
192     }
193   }
194   ~StaticMeta() {
195     LOG(FATAL) << "StaticMeta lives forever!";
196   }
197
198   static void onThreadExit(void* ptr) {
199     auto & meta = instance();
200     DCHECK_EQ(ptr, &meta);
201     // We wouldn't call pthread_setspecific unless we actually called get()
202     DCHECK_NE(threadEntry_.elementsCapacity, 0);
203     {
204       boost::lock_guard<boost::mutex> g(meta.lock_);
205       meta.erase(&threadEntry_);
206       // No need to hold the lock any longer; threadEntry_ is private to this
207       // thread now that it's been removed from meta.
208     }
209     FOR_EACH_RANGE(i, 0, threadEntry_.elementsCapacity) {
210       threadEntry_.elements[i].dispose(TLPDestructionMode::THIS_THREAD);
211     }
212     free(threadEntry_.elements);
213     threadEntry_.elements = NULL;
214     pthread_setspecific(meta.pthreadKey_, NULL);
215   }
216
217   static int create() {
218     int id;
219     auto & meta = instance();
220     boost::lock_guard<boost::mutex> g(meta.lock_);
221     if (!meta.freeIds_.empty()) {
222       id = meta.freeIds_.back();
223       meta.freeIds_.pop_back();
224     } else {
225       id = meta.nextId_++;
226     }
227     return id;
228   }
229
230   static void destroy(int id) {
231     try {
232       auto & meta = instance();
233       // Elements in other threads that use this id.
234       std::vector<ElementWrapper> elements;
235       {
236         boost::lock_guard<boost::mutex> g(meta.lock_);
237         for (ThreadEntry* e = meta.head_.next; e != &meta.head_; e = e->next) {
238           if (id < e->elementsCapacity && e->elements[id].ptr) {
239             elements.push_back(e->elements[id]);
240
241             // Writing another thread's ThreadEntry from here is fine;
242             // the only other potential reader is the owning thread --
243             // from onThreadExit (which grabs the lock, so is properly
244             // synchronized with us) or from get() -- but using get() on a
245             // ThreadLocalPtr object that's being destroyed is a bug, so
246             // undefined behavior is fair game.
247             e->elements[id].ptr = NULL;
248             e->elements[id].deleter = NULL;
249           }
250         }
251         meta.freeIds_.push_back(id);
252       }
253       // Delete elements outside the lock
254       FOR_EACH(it, elements) {
255         it->dispose(TLPDestructionMode::ALL_THREADS);
256       }
257     } catch (...) { // Just in case we get a lock error or something anyway...
258       LOG(WARNING) << "Destructor discarding an exception that was thrown.";
259     }
260   }
261
262   static ElementWrapper& get(int id) {
263     size_t prevSize = threadEntry_.elementsCapacity;
264     if (prevSize <= id) {
265       size_t newSize = static_cast<size_t>((id + 5) * 1.7);
266       auto & meta = instance();
267       ElementWrapper* ptr = NULL;
268       // Rely on jemalloc to zero the memory if possible -- maybe it knows
269       // it's already zeroed and saves us some work.
270       if (!usingJEMalloc() ||
271           prevSize < jemallocMinInPlaceExpandable ||
272           (rallocm(
273               static_cast<void**>(static_cast<void*>(&threadEntry_.elements)),
274               NULL, newSize * sizeof(ElementWrapper), 0,
275               ALLOCM_NO_MOVE | ALLOCM_ZERO) != ALLOCM_SUCCESS)) {
276         // Sigh, must realloc, but we can't call realloc here, as elements is
277         // still linked in meta, so another thread might access invalid memory
278         // after realloc succeeds.  We'll copy by hand and update threadEntry_
279         // under the lock.
280         //
281         // Note that we're using calloc instead of malloc in order to zero
282         // the entire region.  rallocm (ALLOCM_ZERO) will only zero newly
283         // allocated memory, so if a previous allocation allocated more than
284         // we requested, it's our responsibility to guarantee that the tail
285         // is zeroed.  calloc() is simpler than malloc() followed by memset(),
286         // and potentially faster when dealing with a lot of memory, as
287         // it can get already-zeroed pages from the kernel.
288         if ((ptr = static_cast<ElementWrapper*>(
289               calloc(newSize, sizeof(ElementWrapper)))) != NULL) {
290           memcpy(ptr, threadEntry_.elements,
291                  sizeof(ElementWrapper) * prevSize);
292         } else {
293           throw std::bad_alloc();
294         }
295       }
296
297       // Success, update the entry
298       {
299         boost::lock_guard<boost::mutex> g(meta.lock_);
300         if (prevSize == 0) {
301           meta.push_back(&threadEntry_);
302         }
303         if (ptr) {
304           using std::swap;
305           swap(ptr, threadEntry_.elements);
306         }
307         threadEntry_.elementsCapacity = newSize;
308       }
309
310       free(ptr);
311
312       if (prevSize == 0) {
313         pthread_setspecific(meta.pthreadKey_, &meta);
314       }
315     }
316     return threadEntry_.elements[id];
317   }
318 };
319
320 template <class Tag> __thread ThreadEntry StaticMeta<Tag>::threadEntry_ = {0};
321 template <class Tag> StaticMeta<Tag>* StaticMeta<Tag>::inst = nullptr;
322
323 }  // namespace threadlocal_detail
324 }  // namespace folly
325
326 #endif /* FOLLY_DETAIL_THREADLOCALDETAIL_H_ */
327