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 +----------------------------------------------------------------------+
16 #include "hphp/util/light-process.h"
22 #include <boost/thread/barrier.hpp>
24 #include <folly/portability/SysMman.h>
25 #include <folly/portability/SysResource.h>
26 #include <folly/portability/Sockets.h>
27 #include <sys/types.h>
33 #include <folly/portability/Unistd.h>
38 #include <folly/Memory.h>
39 #include <folly/String.h>
41 #include "hphp/util/afdt-util.h"
42 #include "hphp/util/compatibility.h"
43 #include "hphp/util/hardware-counter.h"
44 #include "hphp/util/hugetlb.h"
45 #include "hphp/util/logger.h"
46 #include "hphp/util/process.h"
47 #include "hphp/util/timer.h"
51 ///////////////////////////////////////////////////////////////////////////////
56 __thread LightProcess
* tl_proc
;
57 bool s_trackProcessTimes
= false;
63 char **build_envp(const std::vector
<std::string
> &env
) {
64 char **envp
= nullptr;
65 int size
= env
.size();
67 envp
= (char **)malloc((size
+ 1) * sizeof(char *));
69 for (unsigned int i
= 0; i
< env
.size(); i
++, j
++) {
70 *(envp
+ j
) = (char *)env
[i
].c_str();
72 *(envp
+ j
) = nullptr;
77 void close_fds(const std::vector
<int> &fds
) {
83 template<class Head
, class... Tail
>
84 void lwp_write(int afdt_fd
, const Head
& h
, Tail
&&... args
) {
85 if (afdt::sendRaw(afdt_fd
, h
, std::forward
<Tail
>(args
)...)) {
86 throw Exception("Failed in afdt::sendRaw: %s",
87 folly::errnoStr(errno
).c_str());
91 template<class Head
, class... Tail
>
92 void lwp_read(int afdt_fd
, Head
& h
, Tail
&... args
) {
93 if (afdt::recvRaw(afdt_fd
, h
, args
...)) {
94 throw Exception("Failed in afdt::recvRaw: %s",
95 folly::errnoStr(errno
).c_str());
99 // not thread safe - needs a lock when called from the main
101 int popen_impl(const char* cmd
, const char* mode
, pid_t
* out_pid
) {
103 auto const read
= *mode
== 'r';
104 if (!read
&& *mode
!= 'w') return -1;
106 if (pipe2(p
, O_CLOEXEC
) < 0) {
116 int child_pipe
= read
? 1 : 0;
119 mprotect_1g_pages(PROT_READ
);
120 // If anything goes wrong, let the OOM killer kill this child process.
121 Process::OOMScoreAdj(1000);
122 // replace stdin or stdout with the appropriate end
124 if (p
[child_pipe
] == child_pipe
) {
125 // pretty unlikely, but if it was we must clear CLOEXEC,
126 // and the only way to do that is to dup it to a new fd,
127 // and then dup2 it back
128 p
[child_pipe
] = fcntl(child_pipe
, F_DUPFD_CLOEXEC
, 3);
130 dup2(p
[child_pipe
], child_pipe
);
131 // no need to close p[child_pipe] because of O_CLOEXEC
133 signal(SIGINT
, SIG_DFL
);
136 sigprocmask(SIG_SETMASK
, &eset
, nullptr);
137 execl("/bin/sh", "sh", "-c", cmd
, nullptr);
138 Logger::Warning("Failed to exec: `%s'", cmd
);
143 // close the pipe we're not using
144 close(p
[child_pipe
]);
146 return p
[1-child_pipe
];
149 int64_t ru2microseconds(const rusage
& ru
) {
150 int64_t time_us
= ru
.ru_utime
.tv_usec
;
151 time_us
+= ru
.ru_stime
.tv_usec
;
152 int64_t time_s
= ru
.ru_utime
.tv_sec
;
153 time_s
+= ru
.ru_stime
.tv_sec
;
154 return time_us
+ time_s
* 1000000;
158 * Hardware counters can be configured to measure sub-process times,
159 * but note that any given LightProcess will be running jobs for
160 * multiple request threads (each request thread binds to a single
161 * LightProcess, but its a many to one mapping).
163 * This means that between the fork/exec and the waitpid, the LightProcess
164 * could fork/exec more processes for different requests. The solution
165 * is to fork/exec in a thread, and start the hardware counters there.
167 * The hardware counter will then measure time for that thread, and all
168 * its children - which is exactly what we want.
170 * HardwareCounterWrapper & co take care of that.
172 struct HardwareCounterWrapperArg
{
173 boost::barrier barrier
{2};
181 std::map
<pid_t
, std::unique_ptr
<HardwareCounterWrapperArg
>> s_pidToHCWMap
;
183 void* hardwareCounterWrapper(void* varg
) {
184 auto arg
= (HardwareCounterWrapperArg
*)varg
;
186 HardwareCounter::s_counter
.getCheck();
187 HardwareCounter::Reset();
188 arg
->pid
= arg
->func(arg
->afdt_fd
);
189 // tell the main thread that pid has been set.
191 if (arg
->pid
< 0) return nullptr;
193 // Wait until the main thread is ready to join us.
194 // Note that even though we have multiple threads running in the LightProcess
195 // now, at the time any of the do_* functions are called, any live threads are
196 // waiting here on this barrier; so there is no problem with fork, malloc,
199 HardwareCounter::GetPerfEvents(
200 [](const std::string
& event
, int64_t value
, void* data
) {
201 auto events
= reinterpret_cast<int64_t*>(data
);
202 if (event
== "instructions") {
204 } else if (event
== "loads") {
206 } else if (event
== "stores") {
215 void hardwareCounterWrapperHelper(pid_t (*func
)(int), int afdt_fd
) {
216 if (!s_trackProcessTimes
) {
221 auto arg
= std::make_unique
<HardwareCounterWrapperArg
>();
222 arg
->afdt_fd
= afdt_fd
;
224 if (pthread_create(&arg
->thr
, nullptr, hardwareCounterWrapper
, arg
.get())) {
225 throw Exception("Failed to pthread_create: %s",
226 folly::errnoStr(errno
).c_str());
228 // Wait for the pid to be set. Note that we must not add any code here that
229 // could cause issues for the fork (eg malloc); we should wait on the barrier
230 // immediately after the thread is created.
233 // successfully forked, so don't join until waitpid.
234 auto& map_entry
= s_pidToHCWMap
[arg
->pid
];
235 map_entry
= std::move(arg
);
237 // fork failed, join now.
238 pthread_join(arg
->thr
, nullptr);
242 ///////////////////////////////////////////////////////////////////////////////
243 // shadow process tasks
245 pid_t
do_popen_helper(int afdt_fd
) {
246 std::string mode
, buf
, cwd
;
248 lwp_read(afdt_fd
, mode
, buf
, cwd
);
253 old_cwd
= Process::GetCurrentDirectory();
254 if (old_cwd
!= cwd
) {
255 if (chdir(cwd
.c_str())) {
256 // Ignore chdir failures, because the compiled version might not
257 // have the directory any more.
258 Logger::Warning("Light Process failed chdir to %s.", cwd
.c_str());
265 auto fd
= buf
.empty() ? -1 :
266 popen_impl(buf
.c_str(), mode
.c_str(), &pid
);
268 if (!old_cwd
.empty() && chdir(old_cwd
.c_str())) {
269 // only here if we can't change the cwd back
273 Logger::Error("Light process failed popen: %d (%s).", errno
,
274 folly::errnoStr(errno
).c_str());
275 lwp_write(afdt_fd
, "error");
277 lwp_write(afdt_fd
, "success", pid
);
278 send_fd(afdt_fd
, fd
);
279 // the fd is now owned by the main process, close our copy
285 void do_popen(int afdt_fd
) {
286 hardwareCounterWrapperHelper(do_popen_helper
, afdt_fd
);
289 pid_t
do_proc_open_helper(int afdt_fd
) {
290 std::string cmd
, cwd
;
291 std::vector
<std::string
> env
;
292 std::vector
<int> pvals
;
293 lwp_read(afdt_fd
, cmd
, cwd
, env
, pvals
);
295 std::vector
<int> pkeys
;
296 for (int i
= 0; i
< pvals
.size(); i
++) {
297 int fd
= recv_fd(afdt_fd
);
299 lwp_write(afdt_fd
, "error", (int32_t)EPROTO
);
306 // indicate error if an empty command was received
307 if (cmd
.length() == 0) {
308 lwp_write(afdt_fd
, "error", (int32_t)ENOENT
);
312 // now ready to start the child process
313 pid_t child
= fork();
315 mprotect_1g_pages(PROT_READ
);
316 Process::OOMScoreAdj(1000);
317 for (int i
= 0; i
< pvals
.size(); i
++) {
318 dup2(pkeys
[i
], pvals
[i
]);
320 if (!cwd
.empty() && chdir(cwd
.c_str())) {
321 // non-zero for error
322 // chdir failed, the working directory remains unchanged
325 char **envp
= build_envp(env
);
326 execle("/bin/sh", "sh", "-c", cmd
.c_str(), nullptr, envp
);
329 execl("/bin/sh", "sh", "-c", cmd
.c_str(), nullptr);
331 _Exit(HPHP_EXIT_FAILURE
);
335 // successfully created the child process
336 lwp_write(afdt_fd
, "success", child
);
338 // failed creating the child process
339 lwp_write(afdt_fd
, "error", errno
);
346 void do_proc_open(int afdt_fd
) {
347 hardwareCounterWrapperHelper(do_proc_open_helper
, afdt_fd
);
352 void kill_handler(int sig
) {
353 if (sig
== SIGALRM
&& waited
) {
354 kill(waited
, SIGKILL
);
358 void do_waitpid(int afdt_fd
) {
362 lwp_read(afdt_fd
, pid
, options
, timeout
);
367 signal(SIGALRM
, kill_handler
);
373 const auto ret
= ::wait4(pid
, &stat
, options
, &ru
);
374 alarm(0); // cancel the previous alarm if not triggered yet
376 int64_t events
[] = { 0, 0, 0 };
377 if (ret
> 0 && s_trackProcessTimes
) {
378 time_us
= ru2microseconds(ru
);
379 auto it
= s_pidToHCWMap
.find(ret
);
380 if (it
== s_pidToHCWMap
.end()) {
381 throw Exception("pid not in map: %s",
382 folly::errnoStr(errno
).c_str());
385 auto hcw
= std::move(it
->second
);
386 s_pidToHCWMap
.erase(it
);
387 hcw
->events
= events
;
389 pthread_join(hcw
->thr
, nullptr);
392 lwp_write(afdt_fd
, ret
, errno
, stat
,
393 time_us
, events
[0], events
[1], events
[2]);
396 void do_change_user(int afdt_fd
) {
398 lwp_read(afdt_fd
, uname
);
399 if (uname
.length() > 0) {
400 struct passwd
*pw
= getpwnam(uname
.c_str());
403 initgroups(pw
->pw_name
, pw
->pw_gid
);
413 ///////////////////////////////////////////////////////////////////////////////
414 // light-weight process
416 std::unique_ptr
<LightProcess
[]> g_procs
;
417 int g_procsCount
= 0;
418 bool s_handlerInited
= false;
419 LightProcess::LostChildHandler s_lostChildHandler
;
420 std::map
<FILE*, pid_t
> s_popenMap
;
424 LightProcess::LightProcess() : m_shadowProcess(0), m_afdt_fd(-1) { }
426 LightProcess::~LightProcess() {
429 void LightProcess::SigChldHandler(int /*sig*/, siginfo_t
* info
, void* /*ctx*/) {
430 if (info
->si_code
!= CLD_EXITED
&&
431 info
->si_code
!= CLD_KILLED
&&
432 info
->si_code
!= CLD_DUMPED
) {
435 pid_t pid
= info
->si_pid
;
436 for (int i
= 0; i
< g_procsCount
; ++i
) {
437 if (g_procs
&& g_procs
[i
].m_shadowProcess
== pid
) {
438 // The exited process was a light process. Notify the callback, if any.
439 if (s_lostChildHandler
) {
440 s_lostChildHandler(pid
);
447 void LightProcess::Initialize(const std::string
&prefix
, int count
,
448 bool trackProcessTimes
,
449 const std::vector
<int> &inherited_fds
) {
450 s_trackProcessTimes
= trackProcessTimes
;
452 if (prefix
.empty() || count
<= 0) {
457 // already initialized
461 g_procs
.reset(new LightProcess
[count
]);
462 g_procsCount
= count
;
464 auto afdt_filename
= folly::sformat("{}.{}", prefix
, getpid());
466 // remove the possible leftover
467 remove(afdt_filename
.c_str());
469 afdt_error_t err
= AFDT_ERROR_T_INIT
;
470 auto afdt_lid
= afdt_listen(afdt_filename
.c_str(), &err
);
472 Logger::Error("Unable to afdt_listen to %s: %d %s",
473 afdt_filename
.c_str(),
474 errno
, folly::errnoStr(errno
).c_str());
480 remove(afdt_filename
.c_str());
483 for (int i
= 0; i
< count
; i
++) {
484 if (!g_procs
[i
].initShadow(afdt_lid
, afdt_filename
, i
, inherited_fds
)) {
485 for (int j
= 0; j
< i
; j
++) {
486 g_procs
[j
].closeShadow();
494 if (!s_handlerInited
) {
495 struct sigaction sa
= {};
496 sa
.sa_sigaction
= &LightProcess::SigChldHandler
;
497 sa
.sa_flags
= SA_SIGINFO
| SA_NOCLDSTOP
;
498 if (sigaction(SIGCHLD
, &sa
, nullptr) != 0) {
499 Logger::Error("Couldn't install SIGCHLD handler");
502 s_handlerInited
= true;
506 bool LightProcess::initShadow(int afdt_lid
,
507 const std::string
&afdt_filename
, int id
,
508 const std::vector
<int> &inherited_fds
) {
509 Lock
lock(m_procMutex
);
511 pid_t child
= fork();
514 mprotect_1g_pages(PROT_READ
);
515 if (s_trackProcessTimes
) {
516 HardwareCounter::RecordSubprocessTimes();
519 pid_t sid
= setsid();
521 Logger::Warning("Unable to setsid");
522 _Exit(HPHP_EXIT_FAILURE
);
524 afdt_error_t err
= AFDT_ERROR_T_INIT
;
525 auto afdt_fd
= afdt_connect(afdt_filename
.c_str(), &err
);
527 Logger::Warning("Unable to afdt_connect, filename %s: %d %s",
528 afdt_filename
.c_str(),
529 errno
, folly::errnoStr(errno
).c_str());
530 _Exit(HPHP_EXIT_FAILURE
);
533 // shadow process doesn't use g_procs
534 for (int i
= 0; i
< id
; i
++) {
535 g_procs
[i
].closeFiles();
539 close_fds(inherited_fds
);
541 // Tell the OOM killer never to kill a light process. Killing it will cause
542 // the entire server to exit, and won't free much memory anyway.
543 Process::OOMScoreAdj(-1000);
545 } else if (child
< 0) {
547 Logger::Warning("Unable to fork lightly: %d %s", errno
,
548 folly::errnoStr(errno
).c_str());
552 m_shadowProcess
= child
;
555 socklen_t addrlen
= sizeof(addr
);
556 m_afdt_fd
= accept(afdt_lid
, &addr
, &addrlen
);
558 Logger::Warning("Unable to establish afdt connection: %d %s",
559 errno
, folly::errnoStr(errno
).c_str());
567 void LightProcess::runShadow(int afdt_fd
) {
572 pfd
[0].events
= POLLIN
;
575 int ret
= poll(pfd
, 1, -1);
576 if (ret
< 0 && errno
== EINTR
) {
579 if (pfd
[0].revents
& POLLHUP
) {
580 // no more command can come in
581 Logger::Error("Lost parent, LightProcess exiting");
584 if (pfd
[0].revents
& POLLIN
) {
585 lwp_read(afdt_fd
, buf
);
587 Logger::Verbose("LightProcess exiting upon request");
589 } else if (buf
== "popen") {
591 } else if (buf
== "proc_open") {
592 do_proc_open(afdt_fd
);
593 } else if (buf
== "waitpid") {
595 } else if (buf
== "change_user") {
596 do_change_user(afdt_fd
);
598 Logger::Info("LightProcess got invalid command: %.20s", buf
.c_str());
602 } catch (const std::exception
& e
) {
603 Logger::Error("LightProcess exiting due to exception: %s", e
.what());
605 Logger::Error("LightProcess exiting due to unknown exception");
615 return (long)pthread_self() % g_procsCount
;
619 void handleException(const char* call
) {
622 } catch (const std::exception
& e
) {
623 Logger::Error("LightProcess::%s failed due to exception: %s",
626 Logger::Error("LightProcess::%s failed due to unknown exception",
631 template <class R
, class F1
>
632 R
runLight(const char* call
, F1 body
, R failureResult
) {
634 auto proc
= tl_proc
? tl_proc
: &g_procs
[GetId()];
635 Lock
lock(proc
->mutex());
639 handleException(call
);
641 return failureResult
;
646 void LightProcess::Close() {
647 std::unique_ptr
<LightProcess
[]> procs
;
649 int count
= g_procsCount
;
653 for (int i
= 0; i
< count
; i
++) {
654 procs
[i
].closeShadow();
658 void LightProcess::closeShadow() {
659 Lock
lock(m_procMutex
);
660 if (m_shadowProcess
) {
662 lwp_write(m_afdt_fd
, "exit");
664 handleException("closeShadow");
666 // removes the "zombie" process, so not to interfere with later waits
668 auto r
= ::waitpid(m_shadowProcess
, nullptr, 0);
670 if (r
!= -1 || errno
!= EINTR
) {
680 void LightProcess::closeFiles() {
681 if (m_afdt_fd
>= 0) {
687 bool LightProcess::Available() {
688 if (tl_proc
) return true;
689 return g_procsCount
> 0;
692 FILE *LightProcess::popen(const char *cmd
, const char *type
,
693 const char *cwd
/* = NULL */) {
695 // fallback to normal popen
696 Logger::Verbose("Light-weight fork not available; "
697 "use the heavy one instead.");
699 FILE *f
= LightPopenImpl(cmd
, type
, cwd
);
704 Logger::Verbose("Light-weight fork failed in remote CLI mode.");
707 Logger::Verbose("Light-weight fork failed; use the heavy one instead.");
709 return HeavyPopenImpl(cmd
, type
, cwd
);
712 FILE *LightProcess::HeavyPopenImpl(const char *cmd
, const char *type
,
718 auto old_cwd
= Process::GetCurrentDirectory();
719 if (old_cwd
!= cwd
) {
721 Logger::Warning("Failed to chdir to %s.", cwd
);
723 fd
= popen_impl(cmd
, type
, &pid
);
724 if (chdir(old_cwd
.c_str())) {
725 // error occurred changing cwd back
727 if (fd
< 0) return nullptr;
731 fd
= popen_impl(cmd
, type
, &pid
);
733 if (fd
< 0) return nullptr;
734 auto f
= fdopen(fd
, type
);
741 FILE *LightProcess::LightPopenImpl(const char *cmd
, const char *type
,
743 return runLight("popen", [&] (LightProcess
* proc
) -> FILE* {
744 auto afdt_fd
= proc
->m_afdt_fd
;
745 lwp_write(afdt_fd
, "popen", type
, cmd
, cwd
? cwd
: "");
748 lwp_read(afdt_fd
, buf
);
749 if (buf
== "error") {
754 lwp_read(afdt_fd
, pid
);
756 int fd
= recv_fd(afdt_fd
);
758 Logger::Error("Light process failed to send the file descriptor.");
761 FILE *f
= fdopen(fd
, type
);
763 proc
->m_popenMap
[f
] = pid
;
766 }, static_cast<FILE*>(nullptr));
769 int LightProcess::pclose(FILE *f
) {
773 auto it
= s_popenMap
.find(f
);
774 if (it
== s_popenMap
.end()) return -1;
776 s_popenMap
.erase(it
);
779 if (tl_proc
) return tl_proc
;
780 return &g_procs
[GetId()];
782 Lock
lock(proc
->m_procMutex
);
784 auto it
= proc
->m_popenMap
.find(f
);
785 if (it
== proc
->m_popenMap
.end()) return -1;
787 proc
->m_popenMap
.erase(it
);
792 if (LightProcess::waitpid(pid
, &status
, 0, 0) < 0) return -1;
796 pid_t
LightProcess::proc_open(const char *cmd
, const std::vector
<int> &created
,
797 const std::vector
<int> &desired
,
799 const std::vector
<std::string
> &env
) {
800 always_assert(Available());
801 always_assert(created
.size() == desired
.size());
803 return runLight("proc_open", [&] (LightProcess
* proc
) -> pid_t
{
804 auto fout
= proc
->m_afdt_fd
;
805 lwp_write(fout
, "proc_open", cmd
, cwd
? cwd
: "",
808 bool error_send
= false;
810 for (auto cfd
: created
) {
811 if (!send_fd(proc
->m_afdt_fd
, cfd
)) {
819 auto fin
= proc
->m_afdt_fd
;
821 if (buf
== "error") {
822 lwp_read(fin
, errno
);
824 // On this error, the receiver side returns dummy errno,
825 // use the sender side errno here.
830 always_assert_flog(buf
== "success",
831 "Unexpected message from light process: `{}'", buf
);
836 }, static_cast<pid_t
>(-1));
839 pid_t
LightProcess::waitpid(pid_t pid
, int *stat_loc
, int options
,
842 // light process is not really there
844 const auto ret
= wait4(pid
, stat_loc
, options
, &ru
);
845 if (ret
> 0 && s_trackProcessTimes
) {
846 s_extra_request_nanoseconds
+= ru2microseconds(ru
) * 1000;
851 return runLight("waitpid", [&] (LightProcess
* proc
) -> pid_t
{
852 lwp_write(proc
->m_afdt_fd
, "waitpid", pid
, options
, timeout
);
857 int64_t time_us
, events
[3];
858 lwp_read(proc
->m_afdt_fd
, ret
, err
, stat
,
859 time_us
, events
[0], events
[1], events
[2]);
864 } else if (s_trackProcessTimes
) {
865 s_extra_request_nanoseconds
+= time_us
* 1000;
866 HardwareCounter::IncInstructionCount(events
[0]);
867 HardwareCounter::IncLoadCount(events
[1]);
868 HardwareCounter::IncStoreCount(events
[2]);
872 }, static_cast<pid_t
>(-1));
875 pid_t
LightProcess::pcntl_waitpid(pid_t pid
, int *stat_loc
, int options
) {
877 const auto ret
= wait4(pid
, stat_loc
, options
, &ru
);
878 if (ret
> 0 && s_trackProcessTimes
) {
879 s_extra_request_nanoseconds
+= ru2microseconds(ru
) * 1000;
884 void LightProcess::ChangeUser(int afdt
, const std::string
& username
) {
885 if (!username
.empty()) lwp_write(afdt
, "change_user", username
);
888 void LightProcess::ChangeUser(const std::string
& username
) {
889 if (username
.empty()) return;
890 for (int i
= 0; i
< g_procsCount
; i
++) {
891 Lock
lock(g_procs
[i
].m_procMutex
);
892 lwp_write(g_procs
[i
].m_afdt_fd
, "change_user", username
);
896 void LightProcess::SetLostChildHandler(const LostChildHandler
& handler
) {
897 s_lostChildHandler
= handler
;
900 std::unique_ptr
<LightProcess
> LightProcess::setThreadLocalAfdtOverride(
901 std::unique_ptr
<LightProcess
> p
903 auto ret
= std::unique_ptr
<LightProcess
>(tl_proc
);
904 tl_proc
= p
.release();
908 std::unique_ptr
<LightProcess
> LightProcess::setThreadLocalAfdtOverride(int fd
) {
909 auto ret
= std::unique_ptr
<LightProcess
>(tl_proc
);
910 tl_proc
= new LightProcess
;
911 tl_proc
->m_afdt_fd
= fd
;
915 int LightProcess::createDelegate() {
917 if (socketpair(AF_UNIX
, SOCK_STREAM
, 0, pair
)) {
918 Logger::Warning("Unable to create a unix socket pair: %s",
919 folly::errnoStr(errno
).c_str());
923 pid_t child
= fork();
926 Logger::Warning("Unable to fork delegate process: %s",
927 folly::errnoStr(errno
).c_str());
939 mprotect_1g_pages(PROT_READ
);
940 if (s_trackProcessTimes
) {
941 HardwareCounter::RecordSubprocessTimes();
944 pid_t sid
= setsid();
946 Logger::Warning("Unable to setsid");
947 _Exit(HPHP_EXIT_FAILURE
);
953 int newfd
= dup2(pair
[1], 0);
954 always_assert(newfd
== 0);
962 always_assert(child
> 0);
967 ///////////////////////////////////////////////////////////////////////////////