Your IP : 3.145.103.119
/*
* Phusion Passenger - https://www.phusionpassenger.com/
* Copyright (c) 2011-2018 Phusion Holding B.V.
*
* "Passenger", "Phusion Passenger" and "Union Station" are registered
* trademarks of Phusion Holding B.V.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef _PASSENGER_SPAWNING_KIT_SMART_SPAWNER_H_
#define _PASSENGER_SPAWNING_KIT_SMART_SPAWNER_H_
#include <oxt/thread.hpp>
#include <oxt/system_calls.hpp>
#include <boost/bind/bind.hpp>
#include <boost/make_shared.hpp>
#include <string>
#include <vector>
#include <map>
#include <stdexcept>
#include <dirent.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <signal.h>
#include <unistd.h>
#include <cstring>
#include <cassert>
#include <adhoc_lve.h>
#include <LoggingKit/Logging.h>
#include <Constants.h>
#include <Exceptions.h>
#include <DataStructures/StringKeyTable.h>
#include <ProcessManagement/Utils.h>
#include <SystemTools/ProcessMetricsCollector.h>
#include <SystemTools/SystemTime.h>
#include <FileTools/FileManip.h>
#include <IOTools/BufferedIO.h>
#include <JsonTools/JsonUtils.h>
#include <Utils/ScopeGuard.h>
#include <Utils/AsyncSignalSafeUtils.h>
#include <LveLoggingDecorator.h>
#include <Core/SpawningKit/Spawner.h>
#include <Core/SpawningKit/Exceptions.h>
#include <Core/SpawningKit/PipeWatcher.h>
#include <Core/SpawningKit/Handshake/Session.h>
#include <Core/SpawningKit/Handshake/Prepare.h>
#include <Core/SpawningKit/Handshake/Perform.h>
#include <Core/SpawningKit/Handshake/BackgroundIOCapturer.h>
namespace Passenger {
namespace SpawningKit {
using namespace std;
using namespace boost;
using namespace oxt;
class SmartSpawner: public Spawner {
private:
const string preloaderCommandString;
string preloaderEnvvars;
string preloaderUserInfo;
string preloaderUlimits;
StringKeyTable<string> preloaderAnnotations;
AppPoolOptions options;
// Protects m_lastUsed and pid.
mutable boost::mutex simpleFieldSyncher;
// Protects everything else.
mutable boost::mutex syncher;
// Preloader information.
pid_t pid;
FileDescriptor preloaderStdin;
string socketAddress;
unsigned long long m_lastUsed;
/**
* Behaves like <tt>waitpid(pid, status, WNOHANG)</tt>, but waits at most
* <em>timeout</em> miliseconds for the process to exit.
*/
static int timedWaitpid(pid_t pid, int *status, unsigned long long timeout) {
Timer<SystemTime::GRAN_10MSEC> timer;
int ret;
do {
ret = syscalls::waitpid(pid, status, WNOHANG);
if (ret > 0 || ret == -1) {
return ret;
} else {
syscalls::usleep(10000);
}
} while (timer.elapsed() < timeout);
return 0; // timed out
}
static bool osProcessExists(pid_t pid) {
if (syscalls::kill(pid, 0) == 0) {
/* On some environments, e.g. Heroku, the init process does
* not properly reap adopted zombie processes, which can interfere
* with our process existance check. To work around this, we
* explicitly check whether or not the process has become a zombie.
*/
return !isZombie(pid);
} else {
return errno != ESRCH;
}
}
static bool isZombie(pid_t pid) {
string filename = "/proc/" + toString(pid) + "/status";
FILE *f = fopen(filename.c_str(), "r");
if (f == NULL) {
// Don't know.
return false;
}
bool result = false;
while (!feof(f)) {
char buf[512];
const char *line;
line = fgets(buf, sizeof(buf), f);
if (line == NULL) {
break;
}
if (strcmp(line, "State: Z (zombie)\n") == 0) {
// Is a zombie.
result = true;
break;
}
}
fclose(f);
return result;
}
static string createCommandString(const vector<string> &command) {
string result;
vector<string>::const_iterator it;
vector<string>::const_iterator begin = command.begin();
vector<string>::const_iterator end = command.end();
for (it = begin; it != end; it++) {
if (it != begin) {
result.append(1, ' ');
}
result.append(escapeShell(*it));
}
return result;
}
void setConfigFromAppPoolOptions(Config *config, Json::Value &extraArgs,
const AppPoolOptions &options)
{
Spawner::setConfigFromAppPoolOptions(config, extraArgs, options);
config->spawnMethod = P_STATIC_STRING("smart");
}
struct StdChannelsAsyncOpenState {
const int workDirFd;
oxt::thread *stdinOpenThread;
FileDescriptor stdinFd;
int stdinOpenErrno;
oxt::thread *stdoutAndErrOpenThread;
FileDescriptor stdoutAndErrFd;
int stdoutAndErrOpenErrno;
BackgroundIOCapturerPtr stdoutAndErrCapturer;
StdChannelsAsyncOpenState(int _workDirFd)
: workDirFd(_workDirFd),
stdinOpenThread(NULL),
stdoutAndErrOpenThread(NULL)
{ }
~StdChannelsAsyncOpenState() {
boost::this_thread::disable_interruption di;
boost::this_thread::disable_syscall_interruption dsi;
if (stdinOpenThread != NULL) {
stdinOpenThread->interrupt_and_join();
delete stdinOpenThread;
}
if (stdoutAndErrOpenThread != NULL) {
stdoutAndErrOpenThread->interrupt_and_join();
delete stdoutAndErrOpenThread;
}
}
};
typedef boost::shared_ptr<StdChannelsAsyncOpenState> StdChannelsAsyncOpenStatePtr;
StdChannelsAsyncOpenStatePtr openStdChannelsFifosAsynchronously(
HandshakeSession &session)
{
StdChannelsAsyncOpenStatePtr state = boost::make_shared<StdChannelsAsyncOpenState>(
session.workDirFd);
state->stdinOpenThread = new oxt::thread(boost::bind(
openStdinChannel, state, session.workDir->getPath()),
"FIFO opener: " + session.workDir->getPath() + "/stdin", 1024 * 128);
state->stdoutAndErrOpenThread = new oxt::thread(boost::bind(
openStdoutAndErrChannel, state, session.workDir->getPath()),
"FIFO opener: " + session.workDir->getPath() + "/stdout_and_err", 1024 * 128);
return state;
}
void waitForStdChannelFifosToBeOpenedByPeer(const StdChannelsAsyncOpenStatePtr &state,
HandshakeSession &session, pid_t pid)
{
TRACE_POINT();
MonotonicTimeUsec startTime = SystemTime::getMonotonicUsec();
ScopeGuard guard(boost::bind(adjustTimeout, startTime, &session.timeoutUsec));
try {
if (state->stdinOpenThread->try_join_for(
boost::chrono::microseconds(session.timeoutUsec)))
{
delete state->stdinOpenThread;
state->stdinOpenThread = NULL;
if (state->stdinFd == -1) {
throw SystemException("Error opening FIFO "
+ session.workDir->getPath() + "/stdin",
state->stdinOpenErrno);
} else {
P_LOG_FILE_DESCRIPTOR_PURPOSE(state->stdinFd,
"App " << pid << " (" << options.appRoot
<< ") stdin");
adjustTimeout(startTime, &session.timeoutUsec);
startTime = SystemTime::getMonotonicUsec();
}
} else {
throw TimeoutException("Timeout opening FIFO "
+ session.workDir->getPath() + "/stdin");
}
UPDATE_TRACE_POINT();
if (state->stdoutAndErrOpenThread->try_join_for(
boost::chrono::microseconds(session.timeoutUsec)))
{
delete state->stdoutAndErrOpenThread;
state->stdoutAndErrOpenThread = NULL;
if (state->stdoutAndErrFd == -1) {
throw SystemException("Error opening FIFO "
+ session.workDir->getPath() + "/stdout_and_err",
state->stdoutAndErrOpenErrno);
} else {
P_LOG_FILE_DESCRIPTOR_PURPOSE(state->stdoutAndErrFd,
"App " << pid << " (" << options.appRoot
<< ") stdoutAndErr");
}
} else {
throw TimeoutException("Timeout opening FIFO "
+ session.workDir->getPath() + "/stdout_and_err");
}
state->stdoutAndErrCapturer = boost::make_shared<BackgroundIOCapturer>(
state->stdoutAndErrFd, pid, session.config->appGroupName,
session.config->logFile);
state->stdoutAndErrCapturer->start();
} catch (const boost::system::system_error &e) {
throw SystemException(e.what(), e.code().value());
}
}
static void openStdinChannel(StdChannelsAsyncOpenStatePtr state,
const string &workDir)
{
int fd = syscalls::openat(state->workDirFd, "stdin", O_WRONLY | O_APPEND | O_NOFOLLOW);
int e = errno;
state->stdinFd.assign(fd, __FILE__, __LINE__);
state->stdinOpenErrno = e;
}
static void openStdoutAndErrChannel(StdChannelsAsyncOpenStatePtr state,
const string &workDir)
{
int fd = syscalls::openat(state->workDirFd, "stdout_and_err", O_RDONLY | O_NOFOLLOW);
int e = errno;
state->stdoutAndErrFd.assign(fd, __FILE__, __LINE__);
state->stdoutAndErrOpenErrno = e;
}
bool preloaderStarted() const {
return pid != -1;
}
void startPreloader() {
TRACE_POINT();
boost::this_thread::disable_interruption di;
boost::this_thread::disable_syscall_interruption dsi;
assert(!preloaderStarted());
P_DEBUG("Spawning new preloader: appRoot=" << options.appRoot);
Config config;
Json::Value extraArgs;
try {
setConfigFromAppPoolOptions(&config, extraArgs, options);
config.startCommand = preloaderCommandString;
} catch (const std::exception &originalException) {
Journey journey(SPAWN_THROUGH_PRELOADER, true);
journey.setStepErrored(SPAWNING_KIT_PREPARATION, true);
throw SpawnException(originalException, journey,
&config).finalize();
}
HandshakeSession session(*context, config, START_PRELOADER);
session.journey.setStepInProgress(SPAWNING_KIT_PREPARATION);
try {
internalStartPreloader(config, session, extraArgs);
} catch (const SpawnException &) {
throw;
} catch (const std::exception &originalException) {
session.journey.setStepErrored(SPAWNING_KIT_PREPARATION);
throw SpawnException(originalException, session.journey,
&config).finalize();
}
}
void internalStartPreloader(Config &config, HandshakeSession &session,
const Json::Value &extraArgs)
{
TRACE_POINT();
HandshakePrepare(session, extraArgs).execute();
Pipe stdinChannel = createPipe(__FILE__, __LINE__);
Pipe stdoutAndErrChannel = createPipe(__FILE__, __LINE__);
adhoc_lve::LveEnter scopedLveEnter(LveLoggingDecorator::lveInitOnce(),
session.uid,
config.lveMinUid,
LveLoggingDecorator::lveExitCallback);
LveLoggingDecorator::logLveEnter(scopedLveEnter,
session.uid,
config.lveMinUid);
string agentFilename = context->resourceLocator
->findSupportBinary(AGENT_EXE);
session.journey.setStepPerformed(SPAWNING_KIT_PREPARATION);
session.journey.setStepInProgress(SPAWNING_KIT_FORK_SUBPROCESS);
session.journey.setStepInProgress(SUBPROCESS_BEFORE_FIRST_EXEC);
pid_t pid = syscalls::fork();
if (pid == 0) {
int e;
char buf[1024];
const char *end = buf + sizeof(buf);
namespace ASSU = AsyncSignalSafeUtils;
resetSignalHandlersAndMask();
disableMallocDebugging();
int stdinCopy = dup2(stdinChannel.first, 3);
int stdoutAndErrCopy = dup2(stdoutAndErrChannel.second, 4);
dup2(stdinCopy, 0);
dup2(stdoutAndErrCopy, 1);
dup2(stdoutAndErrCopy, 2);
closeAllFileDescriptors(2);
execlp(agentFilename.c_str(),
agentFilename.c_str(),
"spawn-env-setupper",
session.workDir->getPath().c_str(),
"--before",
(char *) 0);
char *pos = buf;
e = errno;
pos = ASSU::appendData(pos, end, "Cannot execute \"");
pos = ASSU::appendData(pos, end, agentFilename.data(), agentFilename.size());
pos = ASSU::appendData(pos, end, "\": ");
pos = ASSU::appendData(pos, end, ASSU::limitedStrerror(e));
pos = ASSU::appendData(pos, end, " (errno=");
pos = ASSU::appendInteger<int, 10>(pos, end, e);
pos = ASSU::appendData(pos, end, ")\n");
ASSU::printError(buf, pos - buf);
_exit(1);
} else if (pid == -1) {
int e = errno;
UPDATE_TRACE_POINT();
session.journey.setStepErrored(SPAWNING_KIT_FORK_SUBPROCESS);
SpawnException ex(OPERATING_SYSTEM_ERROR, session.journey, &config);
ex.setSummary(StaticString("Cannot fork a new process: ") + strerror(e)
+ " (errno=" + toString(e) + ")");
ex.setAdvancedProblemDetails(StaticString("Cannot fork a new process: ")
+ strerror(e) + " (errno=" + toString(e) + ")");
throw ex.finalize();
} else {
UPDATE_TRACE_POINT();
session.journey.setStepPerformed(SPAWNING_KIT_FORK_SUBPROCESS);
session.journey.setStepInProgress(SPAWNING_KIT_HANDSHAKE_PERFORM);
scopedLveEnter.exit();
P_LOG_FILE_DESCRIPTOR_PURPOSE(stdinChannel.second,
"Preloader " << pid << " (" << options.appRoot << ") stdin");
P_LOG_FILE_DESCRIPTOR_PURPOSE(stdoutAndErrChannel.first,
"Preloader " << pid << " (" << options.appRoot << ") stdoutAndErr");
UPDATE_TRACE_POINT();
ScopeGuard guard(boost::bind(nonInterruptableKillAndWaitpid, pid));
P_DEBUG("Preloader process forked for appRoot=" << options.appRoot
<< ": PID " << pid);
stdinChannel.first.close();
stdoutAndErrChannel.second.close();
HandshakePerform(session, pid, stdinChannel.second,
stdoutAndErrChannel.first).execute();
string envvars, userInfo, ulimits;
// If a new output variable was added to this function,
// then don't forget to also update these locations:
// - the critical section below
// - bottom of stopPreloader()
// - addPreloaderEnvDumps()
HandshakePerform::loadBasicInfoFromEnvDumpDir(session.envDumpDir,
session.envDumpDirFd, envvars, userInfo, ulimits);
string socketAddress = findPreloaderCommandSocketAddress(session);
{
boost::lock_guard<boost::mutex> l(simpleFieldSyncher);
this->pid = pid;
this->socketAddress = socketAddress;
this->preloaderStdin = stdinChannel.second;
this->preloaderEnvvars = envvars;
this->preloaderUserInfo = userInfo;
this->preloaderUlimits = ulimits;
this->preloaderAnnotations = loadAnnotationsFromEnvDumpDir(
session.envDumpDir, session.envDumpAnnotationsDirFd);
}
PipeWatcherPtr watcher = boost::make_shared<PipeWatcher>(
stdoutAndErrChannel.first, "output", config.appGroupName,
config.logFile, pid);
watcher->initialize();
watcher->start();
UPDATE_TRACE_POINT();
guard.clear();
session.journey.setStepPerformed(SPAWNING_KIT_HANDSHAKE_PERFORM);
P_INFO("Preloader for " << options.appRoot <<
" started on PID " << pid <<
", listening on " << socketAddress);
}
}
void stopPreloader() {
TRACE_POINT();
boost::this_thread::disable_interruption di;
boost::this_thread::disable_syscall_interruption dsi;
if (!preloaderStarted()) {
return;
}
preloaderStdin.close(false);
if (timedWaitpid(pid, NULL, 5000) == 0) {
P_DEBUG("Preloader did not exit in time, killing it...");
syscalls::kill(pid, SIGKILL);
syscalls::waitpid(pid, NULL, 0);
}
// Delete socket after the process has exited so that it
// doesn't crash upon deleting a nonexistant file.
if (getSocketAddressType(socketAddress) == SAT_UNIX) {
string filename = parseUnixSocketAddress(socketAddress);
syscalls::unlink(filename.c_str());
}
{
boost::lock_guard<boost::mutex> l(simpleFieldSyncher);
pid = -1;
socketAddress.clear();
preloaderEnvvars.clear();
preloaderUserInfo.clear();
preloaderUlimits.clear();
preloaderAnnotations.clear();
}
}
FileDescriptor connectToPreloader(HandshakeSession &session) {
TRACE_POINT();
FileDescriptor fd(connectToServer(socketAddress, __FILE__, __LINE__), NULL, 0);
P_LOG_FILE_DESCRIPTOR_PURPOSE(fd, "Preloader " << pid
<< " (" << session.config->appRoot << ") connection");
return fd;
}
struct ForkResult {
pid_t pid;
FileDescriptor stdinFd;
FileDescriptor stdoutAndErrFd;
string alreadyReadStdoutAndErrData;
ForkResult()
: pid(-1)
{ }
ForkResult(pid_t _pid, const FileDescriptor &_stdinFd,
const FileDescriptor &_stdoutAndErrFd,
const string &_alreadyReadStdoutAndErrData)
: pid(_pid),
stdinFd(_stdinFd),
stdoutAndErrFd(_stdoutAndErrFd),
alreadyReadStdoutAndErrData(_alreadyReadStdoutAndErrData)
{ }
};
struct PreloaderCrashed {
SystemException *systemException;
IOException *ioException;
PreloaderCrashed(const SystemException &e)
: systemException(new SystemException(e)),
ioException(NULL)
{ }
PreloaderCrashed(const IOException &e)
: systemException(NULL),
ioException(new IOException(e))
{ }
~PreloaderCrashed() {
delete systemException;
delete ioException;
}
const oxt::tracable_exception &getException() const {
if (systemException != NULL) {
return *systemException;
} else {
return *ioException;
}
}
};
ForkResult invokeForkCommand(HandshakeSession &session, JourneyStep &stepToMarkAsErrored) {
TRACE_POINT();
P_ASSERT_EQ(session.journey.getStepInfo(SPAWNING_KIT_PREPARATION).state,
STEP_PERFORMED);
try {
StdChannelsAsyncOpenStatePtr stdChannelsAsyncOpenState =
openStdChannelsFifosAsynchronously(session);
return internalInvokeForkCommand(session, stdChannelsAsyncOpenState,
stepToMarkAsErrored);
} catch (const PreloaderCrashed &crashException1) {
UPDATE_TRACE_POINT();
P_WARN("An error occurred while spawning an application process: "
<< crashException1.getException().what());
P_WARN("The application preloader seems to have crashed,"
" restarting it and trying again...");
session.journey.reset();
try {
stopPreloader();
} catch (const SpawnException &) {
throw;
} catch (const std::exception &originalException) {
session.journey.setStepErrored(SPAWNING_KIT_PREPARATION, true);
SpawnException e(originalException, session.journey, session.config);
e.setSummary(StaticString("Error stopping a crashed preloader: ")
+ originalException.what());
e.setProblemDescriptionHTML(
"<p>The " PROGRAM_NAME " application server tried"
" to start the web application by communicating with a"
" helper process that we call a \"preloader\". However,"
" this helper process crashed unexpectedly. "
SHORT_PROGRAM_NAME " then tried to restart it, but"
" encountered the following error while trying to"
" stop the preloader:</p>"
"<pre>" + escapeHTML(originalException.what()) + "</pre>");
throw e.finalize();
}
UPDATE_TRACE_POINT();
startPreloader();
session.journey.reset();
session.journey.setStepPerformed(SPAWNING_KIT_PREPARATION, true);
UPDATE_TRACE_POINT();
try {
StdChannelsAsyncOpenStatePtr stdChannelsAsyncOpenState =
openStdChannelsFifosAsynchronously(session);
return internalInvokeForkCommand(session, stdChannelsAsyncOpenState,
stepToMarkAsErrored);
} catch (const PreloaderCrashed &crashException2) {
UPDATE_TRACE_POINT();
session.journey.reset();
session.journey.setStepErrored(SPAWNING_KIT_PREPARATION, true);
try {
stopPreloader();
} catch (const SpawnException &) {
throw;
} catch (const std::exception &originalException) {
SpawnException e(originalException, session.journey, session.config);
e.setSummary(StaticString("Error stopping a crashed preloader: ")
+ originalException.what());
e.setProblemDescriptionHTML(
"<p>The " PROGRAM_NAME " application server tried"
" to start the web application by communicating with a"
" helper process that we call a \"preloader\". However,"
" this helper process crashed unexpectedly. "
SHORT_PROGRAM_NAME " then tried to restart it, but"
" encountered the following error while trying to"
" stop the preloader:</p>"
"<pre>" + escapeHTML(originalException.what()) + "</pre>");
throw e.finalize();
}
SpawnException e(crashException2.getException(),
session.journey, session.config);
e.setSummary(StaticString("An application preloader crashed: ") +
crashException2.getException().what());
e.setProblemDescriptionHTML(
"<p>The " PROGRAM_NAME " application server tried"
" to start the web application by communicating with a"
" helper process that we call a \"preloader\". However,"
" this helper process crashed unexpectedly:</p>"
"<pre>" + escapeHTML(crashException2.getException().what())
+ "</pre>");
throw e.finalize();
}
}
}
ForkResult internalInvokeForkCommand(HandshakeSession &session,
const StdChannelsAsyncOpenStatePtr &stdChannelsAsyncOpenState,
JourneyStep &stepToMarkAsErrored)
{
TRACE_POINT();
P_ASSERT_EQ(session.journey.getStepInfo(SPAWNING_KIT_PREPARATION).state,
STEP_PERFORMED);
session.journey.setStepInProgress(SPAWNING_KIT_CONNECT_TO_PRELOADER);
stepToMarkAsErrored = SPAWNING_KIT_CONNECT_TO_PRELOADER;
FileDescriptor fd;
string line;
Json::Value doc;
try {
fd = connectToPreloader(session);
} catch (const SystemException &e) {
throw PreloaderCrashed(e);
} catch (const IOException &e) {
throw PreloaderCrashed(e);
}
session.journey.setStepPerformed(SPAWNING_KIT_CONNECT_TO_PRELOADER);
session.journey.setStepInProgress(SPAWNING_KIT_SEND_COMMAND_TO_PRELOADER);
stepToMarkAsErrored = SPAWNING_KIT_SEND_COMMAND_TO_PRELOADER;
try {
sendForkCommand(session, fd);
} catch (const SystemException &e) {
throw PreloaderCrashed(e);
} catch (const IOException &e) {
throw PreloaderCrashed(e);
}
session.journey.setStepPerformed(SPAWNING_KIT_SEND_COMMAND_TO_PRELOADER);
session.journey.setStepInProgress(SPAWNING_KIT_READ_RESPONSE_FROM_PRELOADER);
stepToMarkAsErrored = SPAWNING_KIT_READ_RESPONSE_FROM_PRELOADER;
try {
line = readForkCommandResponse(session, fd);
} catch (const SystemException &e) {
throw PreloaderCrashed(e);
} catch (const IOException &e) {
throw PreloaderCrashed(e);
}
session.journey.setStepPerformed(SPAWNING_KIT_READ_RESPONSE_FROM_PRELOADER);
session.journey.setStepInProgress(SPAWNING_KIT_PARSE_RESPONSE_FROM_PRELOADER);
stepToMarkAsErrored = SPAWNING_KIT_PARSE_RESPONSE_FROM_PRELOADER;
doc = parseForkCommandResponse(session, line);
session.journey.setStepPerformed(SPAWNING_KIT_PARSE_RESPONSE_FROM_PRELOADER);
session.journey.setStepInProgress(SPAWNING_KIT_PROCESS_RESPONSE_FROM_PRELOADER);
stepToMarkAsErrored = SPAWNING_KIT_PROCESS_RESPONSE_FROM_PRELOADER;
return handleForkCommandResponse(session, stdChannelsAsyncOpenState, doc);
}
void sendForkCommand(HandshakeSession &session, const FileDescriptor &fd) {
TRACE_POINT();
Json::Value doc;
doc["command"] = "spawn";
doc["work_dir"] = session.workDir->getPath();
writeExact(fd, Json::FastWriter().write(doc), &session.timeoutUsec);
}
string readForkCommandResponse(HandshakeSession &session, const FileDescriptor &fd) {
TRACE_POINT();
BufferedIO io(fd);
try {
return io.readLine(10240, &session.timeoutUsec);
} catch (const SecurityException &) {
session.journey.setStepErrored(SPAWNING_KIT_READ_RESPONSE_FROM_PRELOADER);
SpawnException e(INTERNAL_ERROR, session.journey, session.config);
addPreloaderEnvDumps(e);
e.setSummary("The preloader process sent a response that exceeds the maximum size limit.");
e.setProblemDescriptionHTML(
"<p>The " PROGRAM_NAME " application server tried"
" to start the web application by communicating with a"
" helper process that we call a \"preloader\". However,"
" this helper process sent a response that exceeded the"
" internally-defined maximum size limit.</p>");
e.setSolutionDescriptionHTML(
"<p class=\"sole-solution\">"
"This is probably a bug in the preloader process. Please "
"<a href=\"" SUPPORT_URL "\">"
"report this bug</a>."
"</p>");
throw e.finalize();
}
}
Json::Value parseForkCommandResponse(HandshakeSession &session, const string &data) {
TRACE_POINT();
Json::Value doc;
Json::Reader reader;
if (!reader.parse(data, doc)) {
session.journey.setStepErrored(SPAWNING_KIT_PARSE_RESPONSE_FROM_PRELOADER);
SpawnException e(INTERNAL_ERROR, session.journey, session.config);
addPreloaderEnvDumps(e);
e.setSummary("The preloader process sent an unparseable response: " + data);
e.setProblemDescriptionHTML(
"<p>The " PROGRAM_NAME " application server tried"
" to start the web application by communicating with a"
" helper process that we call a \"preloader\". However,"
" this helper process sent a response that looks like"
" gibberish.</p>"
"<p>The response is as follows:</p>"
"<pre>" + escapeHTML(data) + "</pre>");
e.setSolutionDescriptionHTML(
"<p class=\"sole-solution\">"
"This is probably a bug in the preloader process. Please "
"<a href=\"" SUPPORT_URL "\">"
"report this bug</a>."
"</p>");
throw e.finalize();
}
UPDATE_TRACE_POINT();
if (!validateForkCommandResponse(doc)) {
session.journey.setStepErrored(SPAWNING_KIT_PARSE_RESPONSE_FROM_PRELOADER);
SpawnException e(INTERNAL_ERROR, session.journey, session.config);
addPreloaderEnvDumps(e);
e.setSummary("The preloader process sent a response that does not"
" match the expected structure: " + stringifyJson(doc));
e.setProblemDescriptionHTML(
"<p>The " PROGRAM_NAME " application server tried"
" to start the web application by communicating with a"
" helper process that we call a \"preloader\". However,"
" this helper process sent a response that does not match"
" the structure that " SHORT_PROGRAM_NAME " expects.</p>"
"<p>The response is as follows:</p>"
"<pre>" + escapeHTML(doc.toStyledString()) + "</pre>");
e.setSolutionDescriptionHTML(
"<p class=\"sole-solution\">"
"This is probably a bug in the preloader process. Please "
"<a href=\"" SUPPORT_URL "\">"
"report this bug</a>."
"</p>");
throw e.finalize();
}
return doc;
}
bool validateForkCommandResponse(const Json::Value &doc) const {
if (!doc.isObject()) {
return false;
}
if (!doc.isMember("result") || !doc["result"].isString()) {
return false;
}
if (doc["result"].asString() == "ok") {
if (!doc.isMember("pid") || !doc["pid"].isInt()) {
return false;
}
return true;
} else if (doc["result"].asString() == "error") {
if (!doc.isMember("message") || !doc["message"].isString()) {
return false;
}
return true;
} else {
return false;
}
}
ForkResult handleForkCommandResponse(HandshakeSession &session,
const StdChannelsAsyncOpenStatePtr &stdChannelsAsyncOpenState,
const Json::Value &doc)
{
TRACE_POINT();
if (doc["result"].asString() == "ok") {
return handleForkCommandResponseSuccess(session, stdChannelsAsyncOpenState,
doc);
} else {
P_ASSERT_EQ(doc["result"].asString(), "error");
return handleForkCommandResponseError(session, doc);
}
}
ForkResult handleForkCommandResponseSuccess(HandshakeSession &session,
const StdChannelsAsyncOpenStatePtr &stdChannelsAsyncOpenState, const Json::Value &doc)
{
TRACE_POINT();
pid_t spawnedPid = doc["pid"].asInt();
// How do we know the preloader actually forked a process
// instead of reporting the PID of a random other existing process?
// For security reasons we perform a bunch of sanity checks,
// including checking the PID's UID.
if (spawnedPid < 1) {
UPDATE_TRACE_POINT();
session.journey.setStepErrored(SPAWNING_KIT_PROCESS_RESPONSE_FROM_PRELOADER);
SpawnException e(INTERNAL_ERROR, session.journey, session.config);
addPreloaderEnvDumps(e);
e.setSummary("The the preloader said it spawned a process with PID "
+ toString(spawnedPid) + ", which is not allowed.");
e.setSubprocessPid(spawnedPid);
e.setStdoutAndErrData(getBackgroundIOCapturerData(
stdChannelsAsyncOpenState->stdoutAndErrCapturer));
e.setProblemDescriptionHTML(
"<h2>Application process has unexpected PID</h2>"
"<p>The " PROGRAM_NAME " application server tried"
" to start the web application by communicating with a"
" helper process that we call a \"preloader\". However,"
" the preloader reported that it started a process with"
" a PID of " + toString(spawnedPid) + ", which is not"
" allowed.</p>");
if (!session.config->genericApp && session.config->startsUsingWrapper
&& session.config->wrapperSuppliedByThirdParty)
{
e.setSolutionDescriptionHTML(
"<h2>Please report this bug</h2>"
"<p class=\"sole-solution\">"
"This is probably a bug in the preloader process. The preloader "
"wrapper program is not written by the " PROGRAM_NAME " authors, "
"but by a third party. Please report this bug to the author of "
"the preloader wrapper program."
"</p>");
} else {
e.setSolutionDescriptionHTML(
"<h2>Please report this bug</h2>"
"<p class=\"sole-solution\">"
"This is probably a bug in the preloader process. The preloader "
"is an internal tool part of " PROGRAM_NAME ". Please "
"<a href=\"" SUPPORT_URL "\">"
"report this bug</a>."
"</p>");
}
throw e.finalize();
}
UPDATE_TRACE_POINT();
uid_t spawnedUid = getProcessUid(session, spawnedPid,
stdChannelsAsyncOpenState->stdoutAndErrCapturer);
if (spawnedUid != session.uid) {
UPDATE_TRACE_POINT();
session.journey.setStepErrored(SPAWNING_KIT_PROCESS_RESPONSE_FROM_PRELOADER);
SpawnException e(INTERNAL_ERROR, session.journey, session.config);
addPreloaderEnvDumps(e);
e.setSummary("The process that the preloader said it spawned, PID "
+ toString(spawnedPid) + ", has UID " + toString(spawnedUid)
+ ", but the expected UID is " + toString(session.uid));
e.setSubprocessPid(spawnedPid);
e.setStdoutAndErrData(getBackgroundIOCapturerData(
stdChannelsAsyncOpenState->stdoutAndErrCapturer));
e.setProblemDescriptionHTML(
"<h2>Application process has unexpected UID</h2>"
"<p>The " PROGRAM_NAME " application server tried"
" to start the web application by communicating with a"
" helper process that we call a \"preloader\". However,"
" the web application process that the preloader started"
" belongs to the wrong user. The UID of the web"
" application process should be " + toString(session.uid)
+ ", but is actually " + toString(session.uid) + ".</p>");
if (!session.config->genericApp && session.config->startsUsingWrapper
&& session.config->wrapperSuppliedByThirdParty)
{
e.setSolutionDescriptionHTML(
"<h2>Please report this bug</h2>"
"<p class=\"sole-solution\">"
"This is probably a bug in the preloader process. The preloader "
"wrapper program is not written by the " PROGRAM_NAME " authors, "
"but by a third party. Please report this bug to the author of "
"the preloader wrapper program."
"</p>");
} else {
e.setSolutionDescriptionHTML(
"<h2>Please report this bug</h2>"
"<p class=\"sole-solution\">"
"This is probably a bug in the preloader process. The preloader "
"is an internal tool part of " PROGRAM_NAME ". Please "
"<a href=\"" SUPPORT_URL "\">"
"report this bug</a>."
"</p>");
}
throw e.finalize();
}
UPDATE_TRACE_POINT();
ScopeGuard guard(boost::bind(nonInterruptableKillAndWaitpid, spawnedPid));
waitForStdChannelFifosToBeOpenedByPeer(stdChannelsAsyncOpenState,
session, spawnedPid);
UPDATE_TRACE_POINT();
string alreadyReadStdoutAndErrData;
if (stdChannelsAsyncOpenState->stdoutAndErrCapturer != NULL) {
stdChannelsAsyncOpenState->stdoutAndErrCapturer->stop();
alreadyReadStdoutAndErrData = stdChannelsAsyncOpenState->stdoutAndErrCapturer->getData();
}
guard.clear();
return ForkResult(spawnedPid, stdChannelsAsyncOpenState->stdinFd,
stdChannelsAsyncOpenState->stdoutAndErrFd,
alreadyReadStdoutAndErrData);
}
ForkResult handleForkCommandResponseError(HandshakeSession &session,
const Json::Value &doc)
{
session.journey.setStepErrored(SPAWNING_KIT_PROCESS_RESPONSE_FROM_PRELOADER);
SpawnException e(INTERNAL_ERROR, session.journey, session.config);
addPreloaderEnvDumps(e);
e.setSummary("An error occured while starting the web application: "
+ doc["message"].asString());
e.setProblemDescriptionHTML(
"<p>The " PROGRAM_NAME " application server tried to"
" start the web application by communicating with a"
" helper process that we call a \"preloader\". However, "
" this helper process reported an error:</p>"
"<pre>" + escapeHTML(doc["message"].asString()) + "</pre>");
e.setSolutionDescriptionHTML(
"<p class=\"sole-solution\">"
"Please try troubleshooting the problem by studying the"
" <strong>error message</strong> and the"
" <strong>diagnostics</strong> reports. You can also"
" consult <a href=\"" SUPPORT_URL "\">the " SHORT_PROGRAM_NAME
" support resources</a> for help.</p>");
throw e.finalize();
}
void createStdChannelFifos(const HandshakeSession &session) {
const string &workDir = session.workDir->getPath();
createFifo(session, workDir + "/stdin");
createFifo(session, workDir + "/stdout_and_err");
}
void createFifo(const HandshakeSession &session, const string &path) {
int ret;
do {
ret = mkfifo(path.c_str(), 0600);
} while (ret == -1 && errno == EAGAIN);
if (ret == -1) {
int e = errno;
throw FileSystemException("Cannot create FIFO file " + path,
e, path);
}
ret = syscalls::chown(path.c_str(), session.uid, session.gid);
if (ret == -1) {
int e = errno;
throw FileSystemException("Cannot change owner and group on FIFO file " + path,
e, path);
}
}
string getBackgroundIOCapturerData(const BackgroundIOCapturerPtr &capturer) const {
if (capturer != NULL) {
// Sleep shortly to allow the child process to finish writing logs.
syscalls::usleep(50000);
return capturer->getData();
} else {
return string();
}
}
uid_t getProcessUid(HandshakeSession &session, pid_t pid,
const BackgroundIOCapturerPtr &stdoutAndErrCapturer)
{
TRACE_POINT();
uid_t uid = (uid_t) -1;
try {
vector<pid_t> pids;
pids.push_back(pid);
ProcessMetricMap result = ProcessMetricsCollector().collect(pids);
uid = result[pid].uid;
} catch (const ParseException &) {
HandshakePerform::loadJourneyStateFromResponseDir(session, pid, stdoutAndErrCapturer);
session.journey.setStepErrored(SPAWNING_KIT_PROCESS_RESPONSE_FROM_PRELOADER);
SpawnException e(INTERNAL_ERROR, session.journey, session.config);
addPreloaderEnvDumps(e);
e.setSummary("Unable to query the UID of spawned application process "
+ toString(pid) + ": error parsing 'ps' output");
e.setSubprocessPid(pid);
e.setProblemDescriptionHTML(
"<h2>Unable to use 'ps' to query PID " + toString(pid) + "</h2>"
"<p>The " PROGRAM_NAME " application server tried"
" to start the web application. As part of the starting"
" procedure, " SHORT_PROGRAM_NAME " also tried to query"
" the system user ID of the web application process"
" using the operating system's \"ps\" tool. However,"
" this tool returned output that " SHORT_PROGRAM_NAME
" could not understand.</p>");
e.setSolutionDescriptionHTML(
createSolutionDescriptionForProcessMetricsCollectionError());
throw e.finalize();
} catch (const SystemException &originalException) {
HandshakePerform::loadJourneyStateFromResponseDir(session, pid, stdoutAndErrCapturer);
session.journey.setStepErrored(SPAWNING_KIT_PROCESS_RESPONSE_FROM_PRELOADER);
SpawnException e(OPERATING_SYSTEM_ERROR, session.journey, session.config);
addPreloaderEnvDumps(e);
e.setSummary("Unable to query the UID of spawned application process "
+ toString(pid) + "; error capturing 'ps' output: "
+ originalException.what());
e.setSubprocessPid(pid);
e.setProblemDescriptionHTML(
"<h2>Error capturing 'ps' output for PID " + toString(pid) + "</h2>"
"<p>The " PROGRAM_NAME " application server tried"
" to start the web application. As part of the starting"
" procedure, " SHORT_PROGRAM_NAME " also tried to query"
" the system user ID of the web application process."
" This is done by using the operating system's \"ps\""
" tool and by querying operating system APIs and special"
" files. However, an error was encountered while doing"
" one of those things.</p>"
"<p>The error returned by the operating system is as follows:</p>"
"<pre>" + escapeHTML(originalException.what()) + "</pre>");
e.setSolutionDescriptionHTML(
createSolutionDescriptionForProcessMetricsCollectionError());
throw e.finalize();
}
UPDATE_TRACE_POINT();
if (uid == (uid_t) -1) {
if (osProcessExists(pid)) {
HandshakePerform::loadJourneyStateFromResponseDir(session, pid, stdoutAndErrCapturer);
session.journey.setStepErrored(SPAWNING_KIT_PROCESS_RESPONSE_FROM_PRELOADER);
SpawnException e(INTERNAL_ERROR, session.journey, session.config);
addPreloaderEnvDumps(e);
e.setSummary("Unable to query the UID of spawned application process "
+ toString(pid) + ": 'ps' did not report information"
" about this process");
e.setSubprocessPid(pid);
e.setProblemDescriptionHTML(
"<h2>'ps' did not return any information about PID " + toString(pid) + "</h2>"
"<p>The " PROGRAM_NAME " application server tried"
" to start the web application. As part of the starting"
" procedure, " SHORT_PROGRAM_NAME " also tried to query"
" the system user ID of the web application process"
" using the operating system's \"ps\" tool. However,"
" this tool did not return any information about"
" the web application process.</p>");
e.setSolutionDescriptionHTML(
createSolutionDescriptionForProcessMetricsCollectionError());
throw e.finalize();
} else {
HandshakePerform::loadJourneyStateFromResponseDir(session, pid, stdoutAndErrCapturer);
session.journey.setStepErrored(SPAWNING_KIT_PROCESS_RESPONSE_FROM_PRELOADER);
SpawnException e(INTERNAL_ERROR, session.journey, session.config);
addPreloaderEnvDumps(e);
e.setSummary("The application process spawned from the preloader"
" seems to have exited prematurely");
e.setSubprocessPid(pid);
e.setStdoutAndErrData(getBackgroundIOCapturerData(stdoutAndErrCapturer));
e.setProblemDescriptionHTML(
"<h2>Application process exited prematurely</h2>"
"<p>The " PROGRAM_NAME " application server tried"
" to start the web application. As part of the starting"
" procedure, " SHORT_PROGRAM_NAME " also tried to query"
" the system user ID of the web application process"
" using the operating system's \"ps\" tool. However,"
" this tool did not return any information about"
" the web application process.</p>");
e.setSolutionDescriptionHTML(
createSolutionDescriptionForProcessMetricsCollectionError());
throw e.finalize();
}
} else {
return uid;
}
}
static string createSolutionDescriptionForProcessMetricsCollectionError() {
const char *path = getenv("PATH");
if (path == NULL || path[0] == '\0') {
path = "(empty)";
}
return "<div class=\"multiple-solutions\">"
"<h3>Check whether the \"ps\" tool is installed and accessible by "
SHORT_PROGRAM_NAME "</h3>"
"<p>Maybe \"ps\" is not installed. Or maybe it is installed, but "
SHORT_PROGRAM_NAME " cannot find it inside its PATH. Or"
" maybe filesystem permissions disallow " SHORT_PROGRAM_NAME
" from accessing \"ps\". Please check all these factors and"
" fix them if necessary.</p>"
"<p>" SHORT_PROGRAM_NAME "'s PATH is:</p>"
"<pre>" + escapeHTML(path) + "</pre>"
"<h3>Check whether the server is low on resources</h3>"
"<p>Maybe the server is currently low on resources. This would"
" cause the \"ps\" tool to encounter errors. Please study the"
" <em>error message</em> and the <em>diagnostics reports</em> to"
" verify whether this is the case. Key things to check for:</p>"
"<ul>"
"<li>Excessive CPU usage</li>"
"<li>Memory and swap</li>"
"<li>Ulimits</li>"
"</ul>"
"<p>If the server is indeed low on resources, find a way to"
" free up some resources.</p>"
"<h3>Check whether /proc is mounted</h3>"
"<p>On many operating systems including Linux and FreeBSD, \"ps\""
" only works if /proc is mounted. Please check this.</p>"
"<h3>Still no luck?</h3>"
"<p>Please try troubleshooting the problem by studying the"
" <em>diagnostics</em> reports.</p>"
"</div>";
}
static void adjustTimeout(MonotonicTimeUsec startTime, unsigned long long *timeout) {
boost::this_thread::disable_interruption di;
boost::this_thread::disable_syscall_interruption dsi;
MonotonicTimeUsec now = SystemTime::getMonotonicUsec();
assert(now >= startTime);
MonotonicTimeUsec diff = now - startTime;
if (*timeout >= diff) {
*timeout -= diff;
} else {
*timeout = 0;
}
}
static void doClosedir(DIR *dir) {
closedir(dir);
}
static string findPreloaderCommandSocketAddress(const HandshakeSession &session) {
const vector<Result::Socket> &sockets = session.result.sockets;
vector<Result::Socket>::const_iterator it, end = sockets.end();
for (it = sockets.begin(); it != end; it++) {
if (it->protocol == "preloader") {
return it->address;
}
}
return string();
}
static StringKeyTable<string> loadAnnotationsFromEnvDumpDir(const string &envDumpDir,
int envDumpAnnotationsDirFd)
{
string path = envDumpDir + "/annotations";
DIR *dir = opendir(path.c_str());
if (dir == NULL) {
return StringKeyTable<string>();
}
ScopeGuard guard(boost::bind(doClosedir, dir));
StringKeyTable<string> result;
struct dirent *ent;
while ((ent = readdir(dir)) != NULL) {
if (ent->d_name[0] != '.') {
result.insert(ent->d_name, strip(safeReadFile(envDumpAnnotationsDirFd,
ent->d_name, SPAWNINGKIT_MAX_SUBPROCESS_ENVDUMP_SIZE).first), true);
}
}
result.compact();
return result;
}
void addPreloaderEnvDumps(SpawnException &e) const {
e.setPreloaderPid(pid);
e.setPreloaderEnvvars(preloaderEnvvars);
e.setPreloaderUserInfo(preloaderUserInfo);
e.setPreloaderUlimits(preloaderUlimits);
if (e.getSubprocessEnvvars().empty()) {
e.setSubprocessEnvvars(preloaderEnvvars);
}
if (e.getSubprocessUserInfo().empty()) {
e.setSubprocessUserInfo(preloaderUserInfo);
}
if (e.getSubprocessUlimits().empty()) {
e.setSubprocessUlimits(preloaderUlimits);
}
StringKeyTable<string>::ConstIterator it(preloaderAnnotations);
while (*it != NULL) {
e.setAnnotation(it.getKey(), it.getValue(), false);
it.next();
}
}
public:
SmartSpawner(Context *context,
const vector<string> &preloaderCommand,
const AppPoolOptions &_options)
: Spawner(context),
preloaderCommandString(createCommandString(preloaderCommand))
{
if (preloaderCommand.size() < 2) {
throw ArgumentException("preloaderCommand must have at least 2 elements");
}
options = _options.copyAndPersist();
pid = -1;
m_lastUsed = SystemTime::getUsec();
}
virtual ~SmartSpawner() {
boost::lock_guard<boost::mutex> l(syncher);
stopPreloader();
}
virtual Result spawn(const AppPoolOptions &options) {
TRACE_POINT();
P_ASSERT_EQ(options.appType, this->options.appType);
P_ASSERT_EQ(options.appRoot, this->options.appRoot);
P_DEBUG("Spawning new process: appRoot=" << options.appRoot);
possiblyRaiseInternalError(options);
{
boost::lock_guard<boost::mutex> l(simpleFieldSyncher);
m_lastUsed = SystemTime::getUsec();
}
UPDATE_TRACE_POINT();
boost::lock_guard<boost::mutex> l(syncher);
if (!preloaderStarted()) {
UPDATE_TRACE_POINT();
startPreloader();
}
UPDATE_TRACE_POINT();
Config config;
Json::Value extraArgs;
try {
setConfigFromAppPoolOptions(&config, extraArgs, options);
} catch (const std::exception &originalException) {
Journey journey(SPAWN_THROUGH_PRELOADER, true);
journey.setStepErrored(SPAWNING_KIT_PREPARATION, true);
SpawnException e(originalException, journey, &config);
addPreloaderEnvDumps(e);
throw e.finalize();
}
UPDATE_TRACE_POINT();
HandshakeSession session(*context, config, SPAWN_THROUGH_PRELOADER);
session.journey.setStepInProgress(SPAWNING_KIT_PREPARATION);
JourneyStep stepToMarkAsErrored = SPAWNING_KIT_PREPARATION;
try {
UPDATE_TRACE_POINT();
HandshakePrepare prepare(session, extraArgs);
prepare.execute();
createStdChannelFifos(session);
prepare.finalize();
session.journey.setStepPerformed(SPAWNING_KIT_PREPARATION, true);
UPDATE_TRACE_POINT();
ForkResult forkResult = invokeForkCommand(session, stepToMarkAsErrored);
UPDATE_TRACE_POINT();
ScopeGuard guard(boost::bind(nonInterruptableKillAndWaitpid, forkResult.pid));
P_DEBUG("Process forked for appRoot=" << options.appRoot << ": PID " << forkResult.pid);
UPDATE_TRACE_POINT();
session.journey.setStepPerformed(SPAWNING_KIT_PROCESS_RESPONSE_FROM_PRELOADER);
session.journey.setStepInProgress(PRELOADER_PREPARATION);
session.journey.setStepInProgress(SPAWNING_KIT_HANDSHAKE_PERFORM);
stepToMarkAsErrored = SPAWNING_KIT_HANDSHAKE_PERFORM;
HandshakePerform(session, forkResult.pid, forkResult.stdinFd,
forkResult.stdoutAndErrFd, forkResult.alreadyReadStdoutAndErrData).
execute();
guard.clear();
session.journey.setStepPerformed(SPAWNING_KIT_HANDSHAKE_PERFORM);
P_DEBUG("Process spawning done: appRoot=" << options.appRoot <<
", pid=" << forkResult.pid);
return session.result;
} catch (SpawnException &e) {
addPreloaderEnvDumps(e);
throw e;
} catch (const std::exception &originalException) {
session.journey.setStepErrored(stepToMarkAsErrored, true);
SpawnException e(originalException, session.journey,
&config);
addPreloaderEnvDumps(e);
throw e.finalize();
}
}
virtual bool cleanable() const {
return true;
}
virtual void cleanup() {
TRACE_POINT();
{
boost::lock_guard<boost::mutex> l(simpleFieldSyncher);
m_lastUsed = SystemTime::getUsec();
}
boost::lock_guard<boost::mutex> lock(syncher);
stopPreloader();
}
virtual unsigned long long lastUsed() const {
boost::lock_guard<boost::mutex> lock(simpleFieldSyncher);
return m_lastUsed;
}
pid_t getPreloaderPid() const {
boost::lock_guard<boost::mutex> lock(simpleFieldSyncher);
return pid;
}
};
} // namespace SpawningKit
} // namespace Passenger
#endif /* _PASSENGER_SPAWNING_KIT_SMART_SPAWNER_H_ */