2 +----------------------------------------------------------------------+
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>
25 #include <sys/prctl.h>
28 #include "hphp/util/alloc.h"
29 #include "hphp/util/hugetlb.h"
30 #include "hphp/util/maphuge.h"
31 #include "hphp/util/numa.h"
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
,
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
57 throw std::runtime_error
{"extra per-thread storage is too big"};
61 AsyncFuncImpl::~AsyncFuncImpl() {
62 assert(m_stopped
|| m_threadId
== 0);
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();
81 // Allocate a piece of memory using mmap(), with address range [start, end), so
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
,
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
,
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
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.
113 munmap(start
, offset
);
115 if (auto const extraAfterEnd
= start
+ allocSize
- newEnd
) {
116 munmap(newEnd
, extraAfterEnd
);
122 void AsyncFuncImpl::start() {
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
) {
136 throw std::invalid_argument
{"huge stack size exceeds rlimit"};
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 ---> +------------+
152 // | for the | ---------------
155 // pthreads ---> +------------+ huge page |
157 // | TLS | hugeStack |
159 // . . ---------------
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
,
176 nHugePages
? size2m
: size4k
);
177 madvise(m_threadStack
, m_stackAllocSize
, MADV_DONTNEED
);
178 numa_bind_to(m_threadStack
, m_stackAllocSize
, m_node
);
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
};
188 m_tlExtraBase
= m_threadStack
+ rlim
.rlim_cur
;
193 if (!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;
200 m_stackAllocSize
= rlim
.rlim_cur
;
201 madvise(m_threadStack
, m_stackAllocSize
, MADV_DONTNEED
);
202 numa_bind_to(m_threadStack
, m_stackAllocSize
, m_node
);
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);
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());
230 if (!m_stopMonitor
.wait(seconds
)) {
234 } else if (seconds
< 0) {
238 // Wait with no timeout.
239 m_stopMonitor
.wait();
245 pthread_join(m_threadId
, &ret
);
249 if (m_threadStack
!= nullptr) {
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
) {
266 void AsyncFuncImpl::setThreadName() {
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
);
274 // On single-socket servers
275 prctl(PR_SET_NAME
, "hhvmworker");
280 void AsyncFuncImpl::threadFuncImpl() {
281 if (s_initFunc
&& !m_noInitFini
) {
282 s_initFunc(s_initFuncArg
);
286 } catch (Exception
& e
) {
287 m_exception
= e
.clone();
288 } catch (std::exception
& e
) {
289 m_exception
= new Exception(std::string
{e
.what()});
291 m_exception
= new Exception("(unknown exception)");
294 Lock
lock(m_stopMonitor
.getMutex());
296 m_stopMonitor
.notify();
298 if (s_finiFunc
&& !m_noInitFini
) {
299 s_finiFunc(s_finiFuncArg
);
303 ///////////////////////////////////////////////////////////////////////////////