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"
21 #include <boost/scoped_array.hpp>
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 // replace stdin or stdout with the appropriate end
122 if (p
[child_pipe
] == child_pipe
) {
123 // pretty unlikely, but if it was we must clear CLOEXEC,
124 // and the only way to do that is to dup it to a new fd,
125 // and then dup2 it back
126 p
[child_pipe
] = fcntl(child_pipe
, F_DUPFD_CLOEXEC
, 3);
128 dup2(p
[child_pipe
], child_pipe
);
129 // no need to close p[child_pipe] because of O_CLOEXEC
131 signal(SIGINT
, SIG_DFL
);
134 sigprocmask(SIG_SETMASK
, &eset
, nullptr);
135 execl("/bin/sh", "sh", "-c", cmd
, nullptr);
136 Logger::Warning("Failed to exec: `%s'", cmd
);
141 // close the pipe we're not using
142 close(p
[child_pipe
]);
144 return p
[1-child_pipe
];
147 int64_t ru2microseconds(const rusage
& ru
) {
148 int64_t time_us
= ru
.ru_utime
.tv_usec
;
149 time_us
+= ru
.ru_stime
.tv_usec
;
150 int64_t time_s
= ru
.ru_utime
.tv_sec
;
151 time_s
+= ru
.ru_stime
.tv_sec
;
152 return time_us
+ time_s
* 1000000;
156 * Hardware counters can be configured to measure sub-process times,
157 * but note that any given LightProcess will be running jobs for
158 * multiple request threads (each request thread binds to a single
159 * LightProcess, but its a many to one mapping).
161 * This means that between the fork/exec and the waitpid, the LightProcess
162 * could fork/exec more processes for different requests. The solution
163 * is to fork/exec in a thread, and start the hardware counters there.
165 * The hardware counter will then measure time for that thread, and all
166 * its children - which is exactly what we want.
168 * HardwareCounterWrapper & co take care of that.
170 struct HardwareCounterWrapperArg
{
171 boost::barrier barrier
{2};
179 std::map
<pid_t
, std::unique_ptr
<HardwareCounterWrapperArg
>> s_pidToHCWMap
;
181 void* hardwareCounterWrapper(void* varg
) {
182 auto arg
= (HardwareCounterWrapperArg
*)varg
;
184 HardwareCounter::s_counter
.getCheck();
185 HardwareCounter::Reset();
186 arg
->pid
= arg
->func(arg
->afdt_fd
);
187 // tell the main thread that pid has been set.
189 if (arg
->pid
< 0) return nullptr;
191 // Wait until the main thread is ready to join us.
192 // Note that even though we have multiple threads running in the LightProcess
193 // now, at the time any of the do_* functions are called, any live threads are
194 // waiting here on this barrier; so there is no problem with fork, malloc,
197 HardwareCounter::GetPerfEvents(
198 [](const std::string
& event
, int64_t value
, void* data
) {
199 auto events
= reinterpret_cast<int64_t*>(data
);
200 if (event
== "instructions") {
202 } else if (event
== "loads") {
204 } else if (event
== "stores") {
213 void hardwareCounterWrapperHelper(pid_t (*func
)(int), int afdt_fd
) {
214 if (!s_trackProcessTimes
) {
219 auto arg
= std::make_unique
<HardwareCounterWrapperArg
>();
220 arg
->afdt_fd
= afdt_fd
;
222 if (pthread_create(&arg
->thr
, nullptr, hardwareCounterWrapper
, arg
.get())) {
223 throw Exception("Failed to pthread_create: %s",
224 folly::errnoStr(errno
).c_str());
226 // Wait for the pid to be set. Note that we must not add any code here that
227 // could cause issues for the fork (eg malloc); we should wait on the barrier
228 // immediately after the thread is created.
231 // successfully forked, so don't join until waitpid.
232 auto& map_entry
= s_pidToHCWMap
[arg
->pid
];
233 map_entry
= std::move(arg
);
235 // fork failed, join now.
236 pthread_join(arg
->thr
, nullptr);
240 ///////////////////////////////////////////////////////////////////////////////
241 // shadow process tasks
243 pid_t
do_popen_helper(int afdt_fd
) {
244 std::string mode
, buf
, cwd
;
246 lwp_read(afdt_fd
, mode
, buf
, cwd
);
251 old_cwd
= Process::GetCurrentDirectory();
252 if (old_cwd
!= cwd
) {
253 if (chdir(cwd
.c_str())) {
254 // Ignore chdir failures, because the compiled version might not
255 // have the directory any more.
256 Logger::Warning("Light Process failed chdir to %s.", cwd
.c_str());
263 auto fd
= buf
.empty() ? -1 :
264 popen_impl(buf
.c_str(), mode
.c_str(), &pid
);
266 if (!old_cwd
.empty() && chdir(old_cwd
.c_str())) {
267 // only here if we can't change the cwd back
271 Logger::Error("Light process failed popen: %d (%s).", errno
,
272 folly::errnoStr(errno
).c_str());
273 lwp_write(afdt_fd
, "error");
275 lwp_write(afdt_fd
, "success", pid
);
276 send_fd(afdt_fd
, fd
);
277 // the fd is now owned by the main process, close our copy
283 void do_popen(int afdt_fd
) {
284 hardwareCounterWrapperHelper(do_popen_helper
, afdt_fd
);
287 pid_t
do_proc_open_helper(int afdt_fd
) {
288 std::string cmd
, cwd
;
289 std::vector
<std::string
> env
;
290 std::vector
<int> pvals
;
291 lwp_read(afdt_fd
, cmd
, cwd
, env
, pvals
);
293 std::vector
<int> pkeys
;
294 for (int i
= 0; i
< pvals
.size(); i
++) {
295 int fd
= recv_fd(afdt_fd
);
297 lwp_write(afdt_fd
, "error", (int32_t)EPROTO
);
304 // indicate error if an empty command was received
305 if (cmd
.length() == 0) {
306 lwp_write(afdt_fd
, "error", (int32_t)ENOENT
);
310 // now ready to start the child process
311 pid_t child
= fork();
313 mprotect_1g_pages(PROT_READ
);
314 for (int i
= 0; i
< pvals
.size(); i
++) {
315 dup2(pkeys
[i
], pvals
[i
]);
317 if (!cwd
.empty() && chdir(cwd
.c_str())) {
318 // non-zero for error
319 // chdir failed, the working directory remains unchanged
322 char **envp
= build_envp(env
);
323 execle("/bin/sh", "sh", "-c", cmd
.c_str(), nullptr, envp
);
326 execl("/bin/sh", "sh", "-c", cmd
.c_str(), nullptr);
328 _Exit(HPHP_EXIT_FAILURE
);
332 // successfully created the child process
333 lwp_write(afdt_fd
, "success", child
);
335 // failed creating the child process
336 lwp_write(afdt_fd
, "error", errno
);
343 void do_proc_open(int afdt_fd
) {
344 hardwareCounterWrapperHelper(do_proc_open_helper
, afdt_fd
);
349 void kill_handler(int sig
) {
350 if (sig
== SIGALRM
&& waited
) {
351 kill(waited
, SIGKILL
);
355 void do_waitpid(int afdt_fd
) {
359 lwp_read(afdt_fd
, pid
, options
, timeout
);
364 signal(SIGALRM
, kill_handler
);
369 const auto ret
= ::wait4(pid
, &stat
, options
, &ru
);
370 alarm(0); // cancel the previous alarm if not triggered yet
372 const auto time_us
= ru2microseconds(ru
);
373 int64_t events
[] = { 0, 0, 0 };
374 if (ret
> 0 && s_trackProcessTimes
) {
375 auto it
= s_pidToHCWMap
.find(ret
);
376 if (it
== s_pidToHCWMap
.end()) {
377 throw Exception("pid not in map: %s",
378 folly::errnoStr(errno
).c_str());
381 auto hcw
= std::move(it
->second
);
382 s_pidToHCWMap
.erase(it
);
383 hcw
->events
= events
;
385 pthread_join(hcw
->thr
, nullptr);
388 lwp_write(afdt_fd
, ret
, errno
, stat
,
389 time_us
, events
[0], events
[1], events
[2]);
392 void do_change_user(int afdt_fd
) {
394 lwp_read(afdt_fd
, uname
);
395 if (uname
.length() > 0) {
396 struct passwd
*pw
= getpwnam(uname
.c_str());
399 initgroups(pw
->pw_name
, pw
->pw_gid
);
409 ///////////////////////////////////////////////////////////////////////////////
410 // light-weight process
412 boost::scoped_array
<LightProcess
> g_procs
;
413 int g_procsCount
= 0;
414 bool s_handlerInited
= false;
415 LightProcess::LostChildHandler s_lostChildHandler
;
416 std::map
<FILE*, pid_t
> s_popenMap
;
420 LightProcess::LightProcess() : m_shadowProcess(0), m_afdt_fd(-1) { }
422 LightProcess::~LightProcess() {
425 void LightProcess::SigChldHandler(int /*sig*/, siginfo_t
* info
, void* /*ctx*/) {
426 if (info
->si_code
!= CLD_EXITED
&&
427 info
->si_code
!= CLD_KILLED
&&
428 info
->si_code
!= CLD_DUMPED
) {
431 pid_t pid
= info
->si_pid
;
432 for (int i
= 0; i
< g_procsCount
; ++i
) {
433 if (g_procs
&& g_procs
[i
].m_shadowProcess
== pid
) {
434 // The exited process was a light process. Notify the callback, if any.
435 if (s_lostChildHandler
) {
436 s_lostChildHandler(pid
);
443 void LightProcess::Initialize(const std::string
&prefix
, int count
,
444 bool trackProcessTimes
,
445 const std::vector
<int> &inherited_fds
) {
446 s_trackProcessTimes
= trackProcessTimes
;
448 if (prefix
.empty() || count
<= 0) {
453 // already initialized
457 g_procs
.reset(new LightProcess
[count
]);
458 g_procsCount
= count
;
460 auto afdt_filename
= folly::sformat("{}.{}", prefix
, getpid());
462 // remove the possible leftover
463 remove(afdt_filename
.c_str());
465 afdt_error_t err
= AFDT_ERROR_T_INIT
;
466 auto afdt_lid
= afdt_listen(afdt_filename
.c_str(), &err
);
468 Logger::Error("Unable to afdt_listen to %s: %d %s",
469 afdt_filename
.c_str(),
470 errno
, folly::errnoStr(errno
).c_str());
476 remove(afdt_filename
.c_str());
479 for (int i
= 0; i
< count
; i
++) {
480 if (!g_procs
[i
].initShadow(afdt_lid
, afdt_filename
, i
, inherited_fds
)) {
481 for (int j
= 0; j
< i
; j
++) {
482 g_procs
[j
].closeShadow();
490 if (!s_handlerInited
) {
491 struct sigaction sa
= {};
492 sa
.sa_sigaction
= &LightProcess::SigChldHandler
;
493 sa
.sa_flags
= SA_SIGINFO
| SA_NOCLDSTOP
;
494 if (sigaction(SIGCHLD
, &sa
, nullptr) != 0) {
495 Logger::Error("Couldn't install SIGCHLD handler");
498 s_handlerInited
= true;
502 bool LightProcess::initShadow(int afdt_lid
,
503 const std::string
&afdt_filename
, int id
,
504 const std::vector
<int> &inherited_fds
) {
505 Lock
lock(m_procMutex
);
507 pid_t child
= fork();
510 mprotect_1g_pages(PROT_READ
);
511 if (s_trackProcessTimes
) {
512 HardwareCounter::RecordSubprocessTimes();
515 pid_t sid
= setsid();
517 Logger::Warning("Unable to setsid");
518 _Exit(HPHP_EXIT_FAILURE
);
520 afdt_error_t err
= AFDT_ERROR_T_INIT
;
521 auto afdt_fd
= afdt_connect(afdt_filename
.c_str(), &err
);
523 Logger::Warning("Unable to afdt_connect, filename %s: %d %s",
524 afdt_filename
.c_str(),
525 errno
, folly::errnoStr(errno
).c_str());
526 _Exit(HPHP_EXIT_FAILURE
);
529 // shadow process doesn't use g_procs
530 for (int i
= 0; i
< id
; i
++) {
531 g_procs
[i
].closeFiles();
535 close_fds(inherited_fds
);
539 } else if (child
< 0) {
541 Logger::Warning("Unable to fork lightly: %d %s", errno
,
542 folly::errnoStr(errno
).c_str());
546 m_shadowProcess
= child
;
549 socklen_t addrlen
= sizeof(addr
);
550 m_afdt_fd
= accept(afdt_lid
, &addr
, &addrlen
);
552 Logger::Warning("Unable to establish afdt connection: %d %s",
553 errno
, folly::errnoStr(errno
).c_str());
561 void LightProcess::runShadow(int afdt_fd
) {
566 pfd
[0].events
= POLLIN
;
569 int ret
= poll(pfd
, 1, -1);
570 if (ret
< 0 && errno
== EINTR
) {
573 if (pfd
[0].revents
& POLLHUP
) {
574 // no more command can come in
575 Logger::Error("Lost parent, LightProcess exiting");
578 if (pfd
[0].revents
& POLLIN
) {
579 lwp_read(afdt_fd
, buf
);
581 Logger::Verbose("LightProcess exiting upon request");
583 } else if (buf
== "popen") {
585 } else if (buf
== "proc_open") {
586 do_proc_open(afdt_fd
);
587 } else if (buf
== "waitpid") {
589 } else if (buf
== "change_user") {
590 do_change_user(afdt_fd
);
592 Logger::Info("LightProcess got invalid command: %.20s", buf
.c_str());
596 } catch (const std::exception
& e
) {
597 Logger::Error("LightProcess exiting due to exception: %s", e
.what());
599 Logger::Error("LightProcess exiting due to unknown exception");
609 return (long)pthread_self() % g_procsCount
;
613 void handleException(const char* call
) {
616 } catch (const std::exception
& e
) {
617 Logger::Error("LightProcess::%s failed due to exception: %s",
620 Logger::Error("LightProcess::%s failed due to unknown exception",
625 template <class R
, class F1
>
626 R
runLight(const char* call
, F1 body
, R failureResult
) {
628 auto proc
= tl_proc
? tl_proc
: &g_procs
[GetId()];
629 Lock
lock(proc
->mutex());
633 handleException(call
);
635 return failureResult
;
640 void LightProcess::Close() {
641 boost::scoped_array
<LightProcess
> procs
;
643 int count
= g_procsCount
;
647 for (int i
= 0; i
< count
; i
++) {
648 procs
[i
].closeShadow();
652 void LightProcess::closeShadow() {
653 Lock
lock(m_procMutex
);
654 if (m_shadowProcess
) {
656 lwp_write(m_afdt_fd
, "exit");
658 handleException("closeShadow");
660 // removes the "zombie" process, so not to interfere with later waits
662 auto r
= ::waitpid(m_shadowProcess
, nullptr, 0);
664 if (r
!= -1 || errno
!= EINTR
) {
674 void LightProcess::closeFiles() {
675 if (m_afdt_fd
>= 0) {
681 bool LightProcess::Available() {
682 if (tl_proc
) return true;
683 return g_procsCount
> 0;
686 FILE *LightProcess::popen(const char *cmd
, const char *type
,
687 const char *cwd
/* = NULL */) {
689 // fallback to normal popen
690 Logger::Verbose("Light-weight fork not available; "
691 "use the heavy one instead.");
693 FILE *f
= LightPopenImpl(cmd
, type
, cwd
);
698 Logger::Warning("Light-weight fork failed in remote CLI mode.");
701 Logger::Verbose("Light-weight fork failed; use the heavy one instead.");
703 return HeavyPopenImpl(cmd
, type
, cwd
);
706 FILE *LightProcess::HeavyPopenImpl(const char *cmd
, const char *type
,
712 auto old_cwd
= Process::GetCurrentDirectory();
713 if (old_cwd
!= cwd
) {
715 Logger::Warning("Failed to chdir to %s.", cwd
);
717 fd
= popen_impl(cmd
, type
, &pid
);
718 if (chdir(old_cwd
.c_str())) {
719 // error occurred changing cwd back
721 if (fd
< 0) return nullptr;
725 fd
= popen_impl(cmd
, type
, &pid
);
727 if (fd
< 0) return nullptr;
728 auto f
= fdopen(fd
, type
);
735 FILE *LightProcess::LightPopenImpl(const char *cmd
, const char *type
,
737 return runLight("popen", [&] (LightProcess
* proc
) -> FILE* {
738 auto afdt_fd
= proc
->m_afdt_fd
;
739 lwp_write(afdt_fd
, "popen", type
, cmd
, cwd
? cwd
: "");
742 lwp_read(afdt_fd
, buf
);
743 if (buf
== "error") {
748 lwp_read(afdt_fd
, pid
);
750 int fd
= recv_fd(afdt_fd
);
752 Logger::Error("Light process failed to send the file descriptor.");
755 FILE *f
= fdopen(fd
, type
);
757 proc
->m_popenMap
[f
] = pid
;
760 }, static_cast<FILE*>(nullptr));
763 int LightProcess::pclose(FILE *f
) {
767 auto it
= s_popenMap
.find(f
);
768 if (it
== s_popenMap
.end()) return -1;
770 s_popenMap
.erase(it
);
773 if (tl_proc
) return tl_proc
;
774 return &g_procs
[GetId()];
776 Lock
lock(proc
->m_procMutex
);
778 auto it
= proc
->m_popenMap
.find(f
);
779 if (it
== proc
->m_popenMap
.end()) return -1;
781 proc
->m_popenMap
.erase(it
);
786 if (LightProcess::waitpid(pid
, &status
, 0, 0) < 0) return -1;
790 pid_t
LightProcess::proc_open(const char *cmd
, const std::vector
<int> &created
,
791 const std::vector
<int> &desired
,
793 const std::vector
<std::string
> &env
) {
794 always_assert(Available());
795 always_assert(created
.size() == desired
.size());
797 return runLight("proc_open", [&] (LightProcess
* proc
) -> pid_t
{
798 auto fout
= proc
->m_afdt_fd
;
799 lwp_write(fout
, "proc_open", cmd
, cwd
? cwd
: "",
802 bool error_send
= false;
804 for (auto cfd
: created
) {
805 if (!send_fd(proc
->m_afdt_fd
, cfd
)) {
813 auto fin
= proc
->m_afdt_fd
;
815 if (buf
== "error") {
816 lwp_read(fin
, errno
);
818 // On this error, the receiver side returns dummy errno,
819 // use the sender side errno here.
824 always_assert_flog(buf
== "success",
825 "Unexpected message from light process: `{}'", buf
);
830 }, static_cast<pid_t
>(-1));
833 pid_t
LightProcess::waitpid(pid_t pid
, int *stat_loc
, int options
,
836 // light process is not really there
838 const auto ret
= wait4(pid
, stat_loc
, options
, &ru
);
839 if (s_trackProcessTimes
) {
840 s_extra_request_nanoseconds
+= ru2microseconds(ru
) * 1000;
845 return runLight("waitpid", [&] (LightProcess
* proc
) -> pid_t
{
846 lwp_write(proc
->m_afdt_fd
, "waitpid", pid
, options
, timeout
);
851 int64_t time_us
, events
[3];
852 lwp_read(proc
->m_afdt_fd
, ret
, err
, stat
,
853 time_us
, events
[0], events
[1], events
[2]);
858 } else if (s_trackProcessTimes
) {
859 s_extra_request_nanoseconds
+= time_us
* 1000;
860 HardwareCounter::IncInstructionCount(events
[0]);
861 HardwareCounter::IncLoadCount(events
[1]);
862 HardwareCounter::IncStoreCount(events
[2]);
866 }, static_cast<pid_t
>(-1));
869 pid_t
LightProcess::pcntl_waitpid(pid_t pid
, int *stat_loc
, int options
) {
871 const auto ret
= wait4(pid
, stat_loc
, options
, &ru
);
872 if (s_trackProcessTimes
) {
873 s_extra_request_nanoseconds
+= ru2microseconds(ru
) * 1000;
878 void LightProcess::ChangeUser(int afdt
, const std::string
& username
) {
879 if (!username
.empty()) lwp_write(afdt
, "change_user", username
);
882 void LightProcess::ChangeUser(const std::string
& username
) {
883 if (username
.empty()) return;
884 for (int i
= 0; i
< g_procsCount
; i
++) {
885 Lock
lock(g_procs
[i
].m_procMutex
);
886 lwp_write(g_procs
[i
].m_afdt_fd
, "change_user", username
);
890 void LightProcess::SetLostChildHandler(const LostChildHandler
& handler
) {
891 s_lostChildHandler
= handler
;
894 std::unique_ptr
<LightProcess
> LightProcess::setThreadLocalAfdtOverride(
895 std::unique_ptr
<LightProcess
> p
897 auto ret
= std::unique_ptr
<LightProcess
>(tl_proc
);
898 tl_proc
= p
.release();
902 std::unique_ptr
<LightProcess
> LightProcess::setThreadLocalAfdtOverride(int fd
) {
903 auto ret
= std::unique_ptr
<LightProcess
>(tl_proc
);
904 tl_proc
= new LightProcess
;
905 tl_proc
->m_afdt_fd
= fd
;
909 int LightProcess::createDelegate() {
911 if (socketpair(AF_UNIX
, SOCK_STREAM
, 0, pair
)) {
912 Logger::Warning("Unable to create a unix socket pair: %s",
913 folly::errnoStr(errno
).c_str());
917 pid_t child
= fork();
920 Logger::Warning("Unable to fork delegate process: %s",
921 folly::errnoStr(errno
).c_str());
933 mprotect_1g_pages(PROT_READ
);
934 if (s_trackProcessTimes
) {
935 HardwareCounter::RecordSubprocessTimes();
938 pid_t sid
= setsid();
940 Logger::Warning("Unable to setsid");
941 _Exit(HPHP_EXIT_FAILURE
);
948 always_assert(child
> 0);
953 ///////////////////////////////////////////////////////////////////////////////