Add sub-controls for Hack array compat runtime checks
[hiphop-php.git] / hphp / runtime / base / thread-info.cpp
blob0dc53f29443bf7ce6a7858e43cc852a6d1ff0d4c
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/runtime/base/thread-info.h"
19 #include <map>
20 #include <set>
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"
39 namespace HPHP {
40 ///////////////////////////////////////////////////////////////////////////////
42 namespace {
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() {
63 assert(!t_stackbase);
64 t_stackbase = static_cast<char*>(stack_top_ptr());
66 m_coverage = new CodeCoverage();
69 ThreadInfo::~ThreadInfo() {
70 assert(t_stackbase);
71 t_stackbase = nullptr;
73 Lock lock(s_thread_info_mutex);
74 s_thread_infos.erase(this);
75 delete m_coverage;
76 rds::threadExit();
79 void ThreadInfo::init() {
80 m_reqInjectionData.threadInit();
81 rds::threadInit();
82 onSessionInit();
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) {
103 f(thread);
107 int ThreadInfo::SetPendingGCForAllOnRequestThread() {
108 int cnt = 0;
109 ExecutePerThread( [&cnt](ThreadInfo* t) {
110 if ( t->changeGlobalGCStatus(OnRequestWithNoPendingExecution,
111 OnRequestWithPendingExecution)) {
112 t->m_reqInjectionData.setFlag(PendingGCFlag);
113 cnt++;
115 } );
116 return cnt;
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;
132 delete tmp;
135 void ThreadInfo::onSessionExit() {
136 // Clear any timeout handlers to they don't fire when the request has already
137 // been destroyed.
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)) {
147 ee->leakBacktrace();
149 delete tmp;
152 rds::requestExit();
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",
170 RID().getTimeout());
171 auto exceptionStack = createBacktrace(BacktraceArgs()
172 .fromWaitHandle(wh)
173 .withSelf()
174 .withThis()
175 .withMetadata());
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()
188 .fromWaitHandle(wh)
189 .withSelf()
190 .withThis()
191 .withMetadata());
192 return new RequestCPUTimeoutException(exceptionMsg, exceptionStack);
195 static Exception* generate_memory_exceeded_exception(c_WaitableWaitHandle* wh) {
196 auto exceptionStack = createBacktrace(BacktraceArgs()
197 .fromWaitHandle(wh)
198 .withSelf()
199 .withThis()
200 .withMetadata());
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()
209 .fromWaitHandle(wh)
210 .withSelf()
211 .withThis()
212 .withMetadata());
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:
224 return true;
225 case ExecutionContext::ErrorState::ExecutingUserHandler:
226 return false;
228 not_reached();
231 size_t handle_request_surprise(c_WaitableWaitHandle* wh, size_t mask) {
232 NoHandleSurpriseScope::AssertNone(static_cast<SurpriseFlag>(mask));
233 auto& info = TI();
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;
243 if (auto cbFlags =
244 flags & (XenonSignalFlag | MemThresholdFlag | IntervalTimerFlag)) {
245 if (!callbacksOk()) {
246 setSurpriseFlag(static_cast<SurpriseFlag>(cbFlags));
247 flags -= 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);
255 } else {
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
260 // timeout.
261 p.setTimeout(0); // Stop wall timer so we won't time out twice.
262 if (pendingException) {
263 setSurpriseFlag(CPUTimedOutFlag);
264 } else {
265 pendingException = generate_request_cpu_timeout_exception(wh);
268 if (flags & MemExceededFlag) {
269 if (pendingException) {
270 setSurpriseFlag(MemExceededFlag);
271 } else {
272 pendingException = generate_memory_exceeded_exception(wh);
275 if (flags & CLIClientTerminated) {
276 if (pendingException) {
277 setSurpriseFlag(CLIClientTerminated);
278 } else {
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");
288 } else {
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();
306 return flags;