This fixes a bug in PHP/HH's crypt_blowfish implementation that can cause a short...
[hiphop-php.git] / hphp / util / async-func.cpp
blob8282f4362432c0b0590a44a0bbc81d8fc9a72351
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/util/async-func.h"
19 #include <folly/portability/SysTime.h>
20 #include <folly/portability/SysMman.h>
21 #include <folly/portability/SysResource.h>
22 #include <folly/portability/Unistd.h>
24 #ifdef HAVE_NUMA
25 #include <sys/prctl.h>
26 #endif
28 #include "hphp/util/alloc.h"
29 #include "hphp/util/hugetlb.h"
30 #include "hphp/util/maphuge.h"
31 #include "hphp/util/numa.h"
33 namespace HPHP {
34 ///////////////////////////////////////////////////////////////////////////////
36 typedef void PFN_THREAD_FUNC(void *);
38 PFN_THREAD_FUNC* AsyncFuncImpl::s_initFunc = nullptr;
39 void* AsyncFuncImpl::s_initFuncArg = nullptr;
41 PFN_THREAD_FUNC* AsyncFuncImpl::s_finiFunc = nullptr;
42 void* AsyncFuncImpl::s_finiFuncArg = nullptr;
44 std::atomic<uint32_t> AsyncFuncImpl::s_count { 0 };
46 AsyncFuncImpl::AsyncFuncImpl(void *obj, PFN_THREAD_FUNC *func,
47 int numaNode, unsigned hugeStackKb,
48 unsigned tlExtraKb)
49 : m_obj(obj)
50 , m_func(func)
51 , m_node(numaNode)
52 , m_hugeStackKb(hugeStackKb / 4 * 4) // align to 4K page boundary
53 , m_tlExtraKb((tlExtraKb + 3) / 4 * 4) {
54 if (m_tlExtraKb > (128 * 1024)) {
55 // Don't include a big additional per-thread storage to avoid running out of
56 // virtual memory.
57 throw std::runtime_error{"extra per-thread storage is too big"};
61 AsyncFuncImpl::~AsyncFuncImpl() {
62 assert(m_stopped || m_threadId == 0);
63 delete m_exception;
66 void *AsyncFuncImpl::ThreadFunc(void *obj) {
67 auto self = static_cast<AsyncFuncImpl*>(obj);
68 init_stack_limits(self->getThreadAttr());
69 s_tlSpace = MemBlock{self->m_tlExtraBase, self->m_tlExtraKb * 1024};
70 assertx(!s_tlSpace.ptr || s_tlSpace.size);
71 s_hugeRange = self->m_hugePages;
72 assertx(!s_hugeRange.ptr || s_hugeRange.size);
74 set_numa_binding(self->m_node);
75 self->setThreadName();
76 self->threadFuncImpl();
77 return nullptr;
80 #ifdef __linux__
81 // Allocate a piece of memory using mmap(), with address range [start, end), so
82 // that
83 // (1) start + size == end,
84 // (2) (start + alignOffset) % alignment == 0, when alignment is nonzero
85 // (3) the memory can be used for stack, thread-local storage, and heap.
87 // All input should be multiples of 16.
88 static char* mmap_offset_aligned(size_t size, size_t alignOffset,
89 size_t alignment) {
90 assertx(size % 16 == 0 && alignOffset % 16 == 0 && alignment % 16 == 0);
91 assertx(alignOffset <= size);
92 assertx(folly::isPowTwo(alignment));
93 auto const alignMask = alignment - 1;
94 auto const allocSize = size + (alignment > 16) * alignment;
95 char* start = (char*)mmap(nullptr, allocSize,
96 PROT_READ | PROT_WRITE,
97 MAP_PRIVATE | MAP_ANONYMOUS,
98 -1, 0);
99 // Check if `mmap()` returned -1, and throw an exception in that case.
100 folly::checkUnixError(reinterpret_cast<intptr_t>(start),
101 "mmap() failed with length = ", allocSize);
102 if (alignment <= 16) return start;
103 auto const oldAlignPoint = reinterpret_cast<uintptr_t>(start) + alignOffset;
104 // Find out how many bytes we need to shift alignPoint to meet alignment
105 // requirement.
106 auto const offset =
107 ((oldAlignPoint + alignMask) & ~alignMask) - oldAlignPoint;
108 assertx((oldAlignPoint + offset) % alignment == 0);
109 auto const newStart = start + offset;
110 auto const newEnd = newStart + size;
111 // unmap extra space at both ends, if any.
112 if (offset) {
113 munmap(start, offset);
115 if (auto const extraAfterEnd = start + allocSize - newEnd) {
116 munmap(newEnd, extraAfterEnd);
118 return newStart;
120 #endif
122 void AsyncFuncImpl::start() {
123 struct rlimit rlim;
124 if (getrlimit(RLIMIT_STACK, &rlim) != 0 || rlim.rlim_cur == RLIM_INFINITY ||
125 rlim.rlim_cur < kStackSizeMinimum) {
126 rlim.rlim_cur = kStackSizeMinimum;
128 // Limit the size of the stack to something reasonable, to avoid running out
129 // of virtual memory.
130 if (rlim.rlim_cur > kStackSizeMinimum * 16) {
131 rlim.rlim_cur = kStackSizeMinimum * 16;
134 if (m_hugeStackKb * 1024 > rlim.rlim_cur) {
135 #ifndef NDEBUG
136 throw std::invalid_argument{"huge stack size exceeds rlimit"};
137 #else
138 m_hugeStackKb = 0;
139 #endif
141 pthread_attr_init(&m_attr);
143 #if defined(__linux__)
144 if (m_hugeStackKb || m_tlExtraKb) {
145 // If m_hugeStackKb is nonzero but not multiple of the huge page size
146 // (size2m), the rest of the huge page is shared with part of the extra
147 // storage colocated with the stack, like the following.
149 // m_threadStack + m_stackAllocSize ---> +------------+
150 // . extra .
151 // . storage .
152 // | for the | ---------------
153 // | thread | ^
154 // | (RDS/slab) | |
155 // pthreads ---> +------------+ huge page |
156 // | TCB | ^ |
157 // | TLS | hugeStack |
158 // | Stack | v v
159 // . . ---------------
160 // . .
161 // m_threadStack ---> +------------+
163 assertx(m_hugeStackKb % 4 == 0);
164 auto const hugeStartOffset = rlim.rlim_cur - m_hugeStackKb * 1024;
166 constexpr unsigned hugePageSizeKb = 2048u;
167 auto const stackPartialHugeKb = m_hugeStackKb % hugePageSizeKb;
168 auto const nHugePages = m_hugeStackKb / hugePageSizeKb +
169 (stackPartialHugeKb != 0) /* partly stack */;
170 m_stackAllocSize = std::max(
171 rlim.rlim_cur + m_tlExtraKb * 1024,
172 hugeStartOffset + size2m * nHugePages
174 m_threadStack = mmap_offset_aligned(m_stackAllocSize,
175 hugeStartOffset,
176 nHugePages ? size2m : size4k);
177 madvise(m_threadStack, m_stackAllocSize, MADV_DONTNEED);
178 numa_bind_to(m_threadStack, m_stackAllocSize, m_node);
179 if (nHugePages) {
180 auto const hugeStart = m_threadStack + hugeStartOffset;
181 assertx(reinterpret_cast<uintptr_t>(hugeStart) % size2m == 0);
182 for (size_t i = 0; i < nHugePages; i++) {
183 remap_2m(hugeStart + i * size2m, m_node);
185 m_hugePages = MemBlock { hugeStart, nHugePages * size2m };
187 if (m_tlExtraKb) {
188 m_tlExtraBase = m_threadStack + rlim.rlim_cur;
191 #endif
193 if (!m_threadStack) {
194 m_threadStack =
195 (char*)mmap(nullptr, rlim.rlim_cur, PROT_READ | PROT_WRITE,
196 MAP_PRIVATE | MAP_ANON, -1, 0);
197 if (m_threadStack == MAP_FAILED) {
198 m_threadStack = nullptr;
199 } else {
200 m_stackAllocSize = rlim.rlim_cur;
201 madvise(m_threadStack, m_stackAllocSize, MADV_DONTNEED);
202 numa_bind_to(m_threadStack, m_stackAllocSize, m_node);
206 if (m_threadStack) {
207 size_t guardsize;
208 if (pthread_attr_getguardsize(&m_attr, &guardsize) == 0 && guardsize) {
209 mprotect(m_threadStack, guardsize, PROT_NONE);
211 pthread_attr_setstack(&m_attr, m_threadStack, rlim.rlim_cur);
214 pthread_create(&m_threadId, &m_attr, ThreadFunc, (void*)this);
215 assert(m_threadId);
216 s_count++;
219 void AsyncFuncImpl::cancel() {
220 pthread_cancel(m_threadId);
223 bool AsyncFuncImpl::waitForEnd(int seconds /* = 0 */) {
224 if (m_threadId == 0) return true;
227 Lock lock(m_stopMonitor.getMutex());
228 while (!m_stopped) {
229 if (seconds > 0) {
230 if (!m_stopMonitor.wait(seconds)) {
231 // wait timed out
232 return false;
234 } else if (seconds < 0) {
235 // Don't wait.
236 return false;
237 } else {
238 // Wait with no timeout.
239 m_stopMonitor.wait();
244 void *ret = nullptr;
245 pthread_join(m_threadId, &ret);
246 s_count--;
247 m_threadId = 0;
249 if (m_threadStack != nullptr) {
250 size_t guardsize;
251 if (pthread_attr_getguardsize(&m_attr, &guardsize) == 0 && guardsize) {
252 mprotect(m_threadStack, guardsize, PROT_READ | PROT_WRITE);
254 munmap(m_threadStack, m_stackAllocSize);
255 m_threadStack = nullptr;
258 if (Exception* e = m_exception) {
259 m_exception = 0;
260 e->throwException();
263 return true;
266 void AsyncFuncImpl::setThreadName() {
267 #ifdef HAVE_NUMA
268 if (use_numa) {
269 static constexpr size_t kMaxCommNameLen = 16; // TASK_COMM_LEN in kernel
270 char name[kMaxCommNameLen];
271 snprintf(name, sizeof(name), "hhvmworker.ND%d", m_node);
272 prctl(PR_SET_NAME, name);
273 } else {
274 // On single-socket servers
275 prctl(PR_SET_NAME, "hhvmworker");
277 #endif
280 void AsyncFuncImpl::threadFuncImpl() {
281 if (s_initFunc && !m_noInitFini) {
282 s_initFunc(s_initFuncArg);
284 try {
285 m_func(m_obj);
286 } catch (Exception& e) {
287 m_exception = e.clone();
288 } catch (std::exception& e) {
289 m_exception = new Exception(std::string{e.what()});
290 } catch (...) {
291 m_exception = new Exception("(unknown exception)");
294 Lock lock(m_stopMonitor.getMutex());
295 m_stopped = true;
296 m_stopMonitor.notify();
298 if (s_finiFunc && !m_noInitFini) {
299 s_finiFunc(s_finiFuncArg);
303 ///////////////////////////////////////////////////////////////////////////////