Consistency in namespace-closing comments
[folly.git] / folly / fibers / GuardPageAllocator.cpp
1 /*
2  * Copyright 2017 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 "GuardPageAllocator.h"
17
18 #ifndef _WIN32
19 #include <dlfcn.h>
20 #endif
21 #include <signal.h>
22
23 #include <iostream>
24 #include <mutex>
25
26 #include <folly/Singleton.h>
27 #include <folly/SpinLock.h>
28 #include <folly/Synchronized.h>
29 #include <folly/portability/SysMman.h>
30 #include <folly/portability/Unistd.h>
31
32 #include <glog/logging.h>
33
34 namespace folly {
35 namespace fibers {
36
37 /**
38  * Each stack with a guard page creates two memory mappings.
39  * Since this is a limited resource, we don't want to create too many of these.
40  *
41  * The upper bound on total number of mappings created
42  * is kNumGuarded * kMaxInUse.
43  */
44
45 /**
46  * Number of guarded stacks per allocator instance
47  */
48 constexpr size_t kNumGuarded = 100;
49
50 /**
51  * Maximum number of allocator instances with guarded stacks enabled
52  */
53 constexpr size_t kMaxInUse = 100;
54
55 /**
56  * A cache for kNumGuarded stacks of a given size
57  *
58  * Thread safe.
59  */
60 class StackCache {
61  public:
62   explicit StackCache(size_t stackSize) : allocSize_(allocSize(stackSize)) {
63     auto p = ::mmap(
64         nullptr,
65         allocSize_ * kNumGuarded,
66         PROT_READ | PROT_WRITE,
67         MAP_PRIVATE | MAP_ANONYMOUS,
68         -1,
69         0);
70     PCHECK(p != (void*)(-1));
71     storage_ = reinterpret_cast<unsigned char*>(p);
72
73     /* Protect the bottommost page of every stack allocation */
74     for (size_t i = 0; i < kNumGuarded; ++i) {
75       auto allocBegin = storage_ + allocSize_ * i;
76       freeList_.emplace_back(allocBegin, /* protected= */ false);
77     }
78   }
79
80   unsigned char* borrow(size_t size) {
81     std::lock_guard<folly::SpinLock> lg(lock_);
82
83     assert(storage_);
84
85     auto as = allocSize(size);
86     if (as != allocSize_ || freeList_.empty()) {
87       return nullptr;
88     }
89
90     auto p = freeList_.back().first;
91     if (!freeList_.back().second) {
92       PCHECK(0 == ::mprotect(p, pagesize(), PROT_NONE));
93       SYNCHRONIZED(pages, protectedPages()) {
94         pages.insert(reinterpret_cast<intptr_t>(p));
95       }
96     }
97     freeList_.pop_back();
98
99     /* We allocate minimum number of pages required, plus a guard page.
100        Since we use this for stack storage, requested allocation is aligned
101        at the top of the allocated pages, while the guard page is at the bottom.
102
103                -- increasing addresses -->
104              Guard page     Normal pages
105             |xxxxxxxxxx|..........|..........|
106             <- allocSize_ ------------------->
107          p -^                <- size -------->
108                       limit -^
109     */
110     auto limit = p + allocSize_ - size;
111     assert(limit >= p + pagesize());
112     return limit;
113   }
114
115   bool giveBack(unsigned char* limit, size_t size) {
116     std::lock_guard<folly::SpinLock> lg(lock_);
117
118     assert(storage_);
119
120     auto as = allocSize(size);
121     auto p = limit + size - as;
122     if (p < storage_ || p >= storage_ + allocSize_ * kNumGuarded) {
123       /* not mine */
124       return false;
125     }
126
127     assert(as == allocSize_);
128     assert((p - storage_) % allocSize_ == 0);
129     freeList_.emplace_back(p, /* protected= */ true);
130     return true;
131   }
132
133   ~StackCache() {
134     assert(storage_);
135     SYNCHRONIZED(pages, protectedPages()) {
136       for (const auto& item : freeList_) {
137         pages.erase(reinterpret_cast<intptr_t>(item.first));
138       }
139     }
140     PCHECK(0 == ::munmap(storage_, allocSize_ * kNumGuarded));
141   }
142
143   static bool isProtected(intptr_t addr) {
144     // Use a read lock for reading.
145     SYNCHRONIZED_CONST(pages, protectedPages()) {
146       for (const auto& page : pages) {
147         intptr_t pageEnd = intptr_t(page + pagesize());
148         if (page <= addr && addr < pageEnd) {
149           return true;
150         }
151       }
152     }
153     return false;
154   }
155
156  private:
157   folly::SpinLock lock_;
158   unsigned char* storage_{nullptr};
159   size_t allocSize_{0};
160
161   /**
162    * LIFO free list. Each pair contains stack pointer and protected flag.
163    */
164   std::vector<std::pair<unsigned char*, bool>> freeList_;
165
166   static size_t pagesize() {
167     static const size_t pagesize = size_t(sysconf(_SC_PAGESIZE));
168     return pagesize;
169   }
170
171   /* Returns a multiple of pagesize() enough to store size + one guard page */
172   static size_t allocSize(size_t size) {
173     return pagesize() * ((size + pagesize() - 1) / pagesize() + 1);
174   }
175
176   static folly::Synchronized<std::unordered_set<intptr_t>>& protectedPages() {
177     static auto instance =
178         new folly::Synchronized<std::unordered_set<intptr_t>>();
179     return *instance;
180   }
181 };
182
183 #ifndef _WIN32
184
185 namespace {
186
187 struct sigaction oldSigsegvAction;
188
189 void sigsegvSignalHandler(int signum, siginfo_t* info, void*) {
190   if (signum != SIGSEGV) {
191     std::cerr << "GuardPageAllocator signal handler called for signal: "
192               << signum;
193     return;
194   }
195
196   if (info &&
197       StackCache::isProtected(reinterpret_cast<intptr_t>(info->si_addr))) {
198     std::cerr << "folly::fibers Fiber stack overflow detected." << std::endl;
199   }
200
201   // Restore old signal handler and let it handle the signal.
202   sigaction(signum, &oldSigsegvAction, nullptr);
203   raise(signum);
204 }
205
206 bool isInJVM() {
207   auto getCreated = dlsym(RTLD_DEFAULT, "JNI_GetCreatedJavaVMs");
208   return getCreated;
209 }
210
211 void installSignalHandler() {
212   static std::once_flag onceFlag;
213   std::call_once(onceFlag, []() {
214     if (isInJVM()) {
215       // Don't install signal handler, since JVM internal signal handler doesn't
216       // work with SA_ONSTACK
217       return;
218     }
219
220     struct sigaction sa;
221     memset(&sa, 0, sizeof(sa));
222     sigemptyset(&sa.sa_mask);
223     // By default signal handlers are run on the signaled thread's stack.
224     // In case of stack overflow running the SIGSEGV signal handler on
225     // the same stack leads to another SIGSEGV and crashes the program.
226     // Use SA_ONSTACK, so alternate stack is used (only if configured via
227     // sigaltstack).
228     sa.sa_flags |= SA_SIGINFO | SA_ONSTACK;
229     sa.sa_sigaction = &sigsegvSignalHandler;
230     sigaction(SIGSEGV, &sa, &oldSigsegvAction);
231   });
232 }
233 }
234
235 #endif
236
237 class CacheManager {
238  public:
239   static CacheManager& instance() {
240     static auto inst = new CacheManager();
241     return *inst;
242   }
243
244   std::unique_ptr<StackCacheEntry> getStackCache(size_t stackSize) {
245     std::lock_guard<folly::SpinLock> lg(lock_);
246     if (inUse_ < kMaxInUse) {
247       ++inUse_;
248       return std::make_unique<StackCacheEntry>(stackSize);
249     }
250
251     return nullptr;
252   }
253
254  private:
255   folly::SpinLock lock_;
256   size_t inUse_{0};
257
258   friend class StackCacheEntry;
259
260   void giveBack(std::unique_ptr<StackCache> /* stackCache_ */) {
261     assert(inUse_ > 0);
262     --inUse_;
263     /* Note: we can add a free list for each size bucket
264        if stack re-use is important.
265        In this case this needs to be a folly::Singleton
266        to make sure the free list is cleaned up on fork.
267
268        TODO(t7351705): fix Singleton destruction order
269     */
270   }
271 };
272
273 /*
274  * RAII Wrapper around a StackCache that calls
275  * CacheManager::giveBack() on destruction.
276  */
277 class StackCacheEntry {
278  public:
279   explicit StackCacheEntry(size_t stackSize)
280       : stackCache_(std::make_unique<StackCache>(stackSize)) {}
281
282   StackCache& cache() const noexcept {
283     return *stackCache_;
284   }
285
286   ~StackCacheEntry() {
287     CacheManager::instance().giveBack(std::move(stackCache_));
288   }
289
290  private:
291   std::unique_ptr<StackCache> stackCache_;
292 };
293
294 GuardPageAllocator::GuardPageAllocator(bool useGuardPages)
295     : useGuardPages_(useGuardPages) {
296 #ifndef _WIN32
297   installSignalHandler();
298 #endif
299 }
300
301 GuardPageAllocator::~GuardPageAllocator() = default;
302
303 unsigned char* GuardPageAllocator::allocate(size_t size) {
304   if (useGuardPages_ && !stackCache_) {
305     stackCache_ = CacheManager::instance().getStackCache(size);
306   }
307
308   if (stackCache_) {
309     auto p = stackCache_->cache().borrow(size);
310     if (p != nullptr) {
311       return p;
312     }
313   }
314   return fallbackAllocator_.allocate(size);
315 }
316
317 void GuardPageAllocator::deallocate(unsigned char* limit, size_t size) {
318   if (!(stackCache_ && stackCache_->cache().giveBack(limit, size))) {
319     fallbackAllocator_.deallocate(limit, size);
320   }
321 }
322 } // namespace fibers
323 } // namespace folly