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/runtime/base/thread-info.h"
22 #include <folly/Format.h>
24 #include "hphp/util/alloc.h"
25 #include "hphp/util/lock.h"
26 #include "hphp/util/perf-event.h"
28 #include "hphp/runtime/base/backtrace.h"
29 #include "hphp/runtime/base/builtin-functions.h"
30 #include "hphp/runtime/base/code-coverage.h"
31 #include "hphp/runtime/base/perf-mem-event.h"
32 #include "hphp/runtime/base/rds.h"
33 #include "hphp/runtime/base/surprise-flags.h"
34 #include "hphp/runtime/server/cli-server.h"
35 #include "hphp/runtime/vm/vm-regs.h"
37 #include "hphp/runtime/ext/process/ext_process.h"
40 ///////////////////////////////////////////////////////////////////////////////
43 ///////////////////////////////////////////////////////////////////////////////
46 * Set of all ThreadInfos for the running process.
48 std::set
<ThreadInfo
*> s_thread_infos
;
49 Mutex s_thread_info_mutex
;
52 * Either null, or populated by initialization of ThreadInfo as an approximation
53 * of the highest address of the current thread's stack.
55 __thread
char* t_stackbase
= nullptr;
57 ///////////////////////////////////////////////////////////////////////////////
60 THREAD_LOCAL_NO_CHECK(ThreadInfo
, ThreadInfo::s_threadInfo
);
62 ThreadInfo::ThreadInfo() {
64 t_stackbase
= static_cast<char*>(stack_top_ptr());
66 m_coverage
= new CodeCoverage();
69 ThreadInfo::~ThreadInfo() {
71 t_stackbase
= nullptr;
73 Lock
lock(s_thread_info_mutex
);
74 s_thread_infos
.erase(this);
79 void ThreadInfo::init() {
80 m_reqInjectionData
.threadInit();
83 // TODO(20427335): Get rid of the illogical onSessionInit() call above.
84 Lock
lock(s_thread_info_mutex
);
85 s_thread_infos
.insert(this);
88 bool ThreadInfo::valid(ThreadInfo
* info
) {
89 Lock
lock(s_thread_info_mutex
);
90 return s_thread_infos
.find(info
) != s_thread_infos
.end();
93 void ThreadInfo::GetExecutionSamples(std::map
<Executing
, int>& counts
) {
94 Lock
lock(s_thread_info_mutex
);
95 for (auto const info
: s_thread_infos
) {
96 ++counts
[info
->m_executing
];
100 void ThreadInfo::ExecutePerThread(std::function
<void(ThreadInfo
*)> f
) {
101 Lock
lock(s_thread_info_mutex
);
102 for (auto thread
: s_thread_infos
) {
107 int ThreadInfo::SetPendingGCForAllOnRequestThread() {
109 ExecutePerThread( [&cnt
](ThreadInfo
* t
) {
110 if ( t
->changeGlobalGCStatus(OnRequestWithNoPendingExecution
,
111 OnRequestWithPendingExecution
)) {
112 t
->m_reqInjectionData
.setFlag(PendingGCFlag
);
119 void ThreadInfo::onSessionInit() {
120 m_reqInjectionData
.onSessionInit();
123 bool ThreadInfo::changeGlobalGCStatus(GlobalGCStatus from
, GlobalGCStatus to
) {
124 return m_globalGCStatus
.compare_exchange_strong(from
, to
);
127 void ThreadInfo::setPendingException(Exception
* e
) {
128 m_reqInjectionData
.setFlag(PendingExceptionFlag
);
130 auto tmp
= m_pendingException
;
131 m_pendingException
= e
;
135 void ThreadInfo::onSessionExit() {
136 // Clear any timeout handlers to they don't fire when the request has already
138 m_reqInjectionData
.setTimeout(0);
139 m_reqInjectionData
.setCPUTimeout(0);
141 m_reqInjectionData
.reset();
143 if (auto tmp
= m_pendingException
) {
144 m_pendingException
= nullptr;
145 // request memory has already been freed
146 if (auto ee
= dynamic_cast<ExtendedException
*>(tmp
)) {
155 //////////////////////////////////////////////////////////////////////
157 void raise_infinite_recursion_error() {
158 if (!RuntimeOption::NoInfiniteRecursionDetection
) {
159 // Reset profiler otherwise it might recurse further causing segfault.
160 TI().m_profiler
= nullptr;
161 raise_error("infinite recursion detected");
165 static Exception
* generate_request_timeout_exception(c_WaitableWaitHandle
* wh
) {
166 auto exceptionMsg
= folly::sformat(
167 !RuntimeOption::ServerExecutionMode() || is_cli_mode()
168 ? "Maximum execution time of {} seconds exceeded"
169 : "entire web request took longer than {} seconds and timed out",
171 auto exceptionStack
= createBacktrace(BacktraceArgs()
176 return new RequestTimeoutException(exceptionMsg
, exceptionStack
);
179 static Exception
* generate_request_cpu_timeout_exception(
180 c_WaitableWaitHandle
* wh
182 auto exceptionMsg
= folly::sformat(
183 "Maximum CPU time of {} seconds exceeded",
184 RID().getCPUTimeout()
187 auto exceptionStack
= createBacktrace(BacktraceArgs()
192 return new RequestCPUTimeoutException(exceptionMsg
, exceptionStack
);
195 static Exception
* generate_memory_exceeded_exception(c_WaitableWaitHandle
* wh
) {
196 auto exceptionStack
= createBacktrace(BacktraceArgs()
201 return new RequestMemoryExceededException(
202 "request has exceeded memory limit", exceptionStack
);
205 static Exception
* generate_cli_client_terminated_exception(
206 c_WaitableWaitHandle
* wh
208 auto exceptionStack
= createBacktrace(BacktraceArgs()
213 return new FatalErrorException("CLI client terminated", exceptionStack
);
216 // suppress certain callbacks when we're running a user error handler;
217 // to reduce the chances that a subsequent error occurs in the callback
218 // and obscures the effect that the first handler would have had.
219 static bool callbacksOk() {
220 switch (g_context
->getErrorState()) {
221 case ExecutionContext::ErrorState::NoError
:
222 case ExecutionContext::ErrorState::ErrorRaised
:
223 case ExecutionContext::ErrorState::ErrorRaisedByUserHandler
:
225 case ExecutionContext::ErrorState::ExecutingUserHandler
:
231 size_t handle_request_surprise(c_WaitableWaitHandle
* wh
, size_t mask
) {
232 NoHandleSurpriseScope::AssertNone(static_cast<SurpriseFlag
>(mask
));
234 auto& p
= info
.m_reqInjectionData
;
236 auto flags
= fetchAndClearSurpriseFlags() & mask
;
237 auto const debugging
= p
.getDebuggerAttached();
239 // Start with any pending exception that might be on the thread.
240 auto pendingException
= info
.m_pendingException
;
241 info
.m_pendingException
= nullptr;
244 flags
& (XenonSignalFlag
| MemThresholdFlag
| IntervalTimerFlag
)) {
245 if (!callbacksOk()) {
246 setSurpriseFlag(static_cast<SurpriseFlag
>(cbFlags
));
251 if ((flags
& TimedOutFlag
) && !debugging
) {
252 p
.setCPUTimeout(0); // Stop CPU timer so we won't time out twice.
253 if (pendingException
) {
254 setSurpriseFlag(TimedOutFlag
);
256 pendingException
= generate_request_timeout_exception(wh
);
258 } else if ((flags
& CPUTimedOutFlag
) && !debugging
) {
259 // Don't bother with the CPU timeout if we're already handling a wall
261 p
.setTimeout(0); // Stop wall timer so we won't time out twice.
262 if (pendingException
) {
263 setSurpriseFlag(CPUTimedOutFlag
);
265 pendingException
= generate_request_cpu_timeout_exception(wh
);
268 if (flags
& MemExceededFlag
) {
269 if (pendingException
) {
270 setSurpriseFlag(MemExceededFlag
);
272 pendingException
= generate_memory_exceeded_exception(wh
);
275 if (flags
& CLIClientTerminated
) {
276 if (pendingException
) {
277 setSurpriseFlag(CLIClientTerminated
);
279 pendingException
= generate_cli_client_terminated_exception(wh
);
282 if (flags
& PendingGCFlag
) {
283 if (StickyFlags
& PendingGCFlag
) {
284 clearSurpriseFlag(PendingGCFlag
);
286 if (tl_heap
->isGCEnabled()) {
287 tl_heap
->collect("surprise");
289 tl_heap
->checkHeap("surprise");
292 if (flags
& SignaledFlag
) {
293 HHVM_FN(pcntl_signal_dispatch
)();
296 if (flags
& PendingPerfEventFlag
) {
297 if (StickyFlags
& PendingPerfEventFlag
) {
298 clearSurpriseFlag(PendingPerfEventFlag
);
300 perf_event_consume(record_perf_mem_event
);
303 if (pendingException
) {
304 pendingException
->throwException();