Your IP : 18.116.24.238
/*
* 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_EXCEPTIONS_H_
#define _PASSENGER_SPAWNING_KIT_EXCEPTIONS_H_
#include <oxt/tracable_exception.hpp>
#include <string>
#include <stdexcept>
#include <limits>
#include <Constants.h>
#include <Exceptions.h>
#include <LoggingKit/LoggingKit.h>
#include <DataStructures/StringKeyTable.h>
#include <ProcessManagement/Spawn.h>
#include <SystemTools/SystemMetricsCollector.h>
#include <StrIntTools/StrIntUtils.h>
#include <Core/SpawningKit/Config.h>
#include <Core/SpawningKit/Journey.h>
extern "C" {
extern char **environ;
}
namespace Passenger {
namespace SpawningKit {
using namespace std;
using namespace oxt;
enum ErrorCategory {
INTERNAL_ERROR,
FILE_SYSTEM_ERROR,
OPERATING_SYSTEM_ERROR,
IO_ERROR,
TIMEOUT_ERROR,
UNKNOWN_ERROR_CATEGORY
};
inline StaticString errorCategoryToString(ErrorCategory category);
inline ErrorCategory inferErrorCategoryFromAnotherException(const std::exception &e,
JourneyStep failedJourneyStep);
/**
* For an introduction see README.md, section "Error reporting".
*/
class SpawnException: public oxt::tracable_exception {
private:
struct EnvDump {
pid_t pid;
string envvars;
string userInfo;
string ulimits;
EnvDump()
: pid(-1)
{ }
};
ErrorCategory category;
Journey journey;
Config config;
string summary;
string advancedProblemDetails;
string problemDescription;
string solutionDescription;
string stdoutAndErrData;
string id;
EnvDump parentProcessEnvDump;
EnvDump preloaderEnvDump;
EnvDump subprocessEnvDump;
string systemMetrics;
StringKeyTable<string> annotations;
static string createDefaultSummary(ErrorCategory category,
const Journey &journey, const StaticString &advancedProblemDetails)
{
string message;
switch (category) {
case TIMEOUT_ERROR:
// We only return a single error message instead of a customized
// one based on the failed step, because the timeout
// applies to the entire journey, not just to a specific step.
// A timeout at a specific step could be the result of a previous
// step taking too much time.
// The way to debug a timeout error is by looking at the timings
// of each step.
switch (journey.getType()) {
case START_PRELOADER:
message = "A timeout occurred while starting a preloader process";
break;
default:
message = "A timeout occurred while spawning an application process";
break;
}
break;
default:
string categoryPhraseWithIndefiniteArticle =
getErrorCategoryPhraseWithIndefiniteArticle(
category, true);
switch (journey.getType()) {
case START_PRELOADER:
switch (journey.getFirstFailedStep()) {
case SPAWNING_KIT_PREPARATION:
message = categoryPhraseWithIndefiniteArticle
+ " occurred while preparing to start a preloader process";
break;
default:
message = categoryPhraseWithIndefiniteArticle
+ " occurred while starting a preloader process";
break;
}
break;
default:
switch (journey.getFirstFailedStep()) {
case SPAWNING_KIT_PREPARATION:
message = categoryPhraseWithIndefiniteArticle
+ " occurred while preparing to spawn an application process";
break;
case SPAWNING_KIT_FORK_SUBPROCESS:
message = categoryPhraseWithIndefiniteArticle
+ " occurred while creating (forking) subprocess";
break;
case SPAWNING_KIT_CONNECT_TO_PRELOADER:
message = categoryPhraseWithIndefiniteArticle
+ " occurred while connecting to the preloader process";
break;
case SPAWNING_KIT_SEND_COMMAND_TO_PRELOADER:
message = categoryPhraseWithIndefiniteArticle
+ " occurred while sending a command to the preloader process";
break;
case SPAWNING_KIT_READ_RESPONSE_FROM_PRELOADER:
message = categoryPhraseWithIndefiniteArticle
+ " occurred while receiving a response from the preloader process";
break;
case SPAWNING_KIT_PARSE_RESPONSE_FROM_PRELOADER:
message = categoryPhraseWithIndefiniteArticle
+ " occurred while parsing a response from the preloader process";
break;
case SPAWNING_KIT_PROCESS_RESPONSE_FROM_PRELOADER:
message = categoryPhraseWithIndefiniteArticle
+ " occurred while processing a response from the preloader process";
break;
default:
message = categoryPhraseWithIndefiniteArticle
+ " occurred while spawning an application process";
break;
}
break;
}
break;
}
if (advancedProblemDetails.empty()) {
message.append(".");
} else {
message.append(": ");
message.append(advancedProblemDetails);
}
return message;
}
static string createDefaultProblemDescription(ErrorCategory category,
const Journey &journey, const Config &config,
const StaticString &advancedProblemDetails,
const StaticString &stdoutAndErrData)
{
StaticString categoryStringWithIndefiniteArticle =
getErrorCategoryPhraseWithIndefiniteArticle(category,
false);
switch (category) {
case INTERNAL_ERROR:
case OPERATING_SYSTEM_ERROR:
case IO_ERROR:
switch (journey.getType()) {
case START_PRELOADER:
switch (journey.getFirstFailedStep()) {
case SPAWNING_KIT_PREPARATION:
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application. In doing so, "
SHORT_PROGRAM_NAME " had to first start an internal"
" helper tool called the \"preloader\". But "
SHORT_PROGRAM_NAME " encountered "
+ categoryStringWithIndefiniteArticle +
" while performing this preparation work",
category, advancedProblemDetails, stdoutAndErrData);
case SPAWNING_KIT_FORK_SUBPROCESS:
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application. But " SHORT_PROGRAM_NAME
" encountered " + categoryStringWithIndefiniteArticle
+ " while creating a subprocess",
category, advancedProblemDetails, stdoutAndErrData);
case SPAWNING_KIT_HANDSHAKE_PERFORM:
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application. In doing so, "
SHORT_PROGRAM_NAME " first started an internal"
" helper tool called the \"preloader\". But "
SHORT_PROGRAM_NAME " encountered "
+ categoryStringWithIndefiniteArticle +
" while communicating with"
" this tool about its startup",
category, advancedProblemDetails, stdoutAndErrData);
case SUBPROCESS_BEFORE_FIRST_EXEC:
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application. In doing so, "
SHORT_PROGRAM_NAME " had to first start an internal"
" helper tool called the \"preloader\". But"
" the subprocess which was supposed to execute this"
" preloader encountered "
+ categoryStringWithIndefiniteArticle,
category, advancedProblemDetails, stdoutAndErrData);
case SUBPROCESS_OS_SHELL:
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application. In doing so, "
SHORT_PROGRAM_NAME " had to first start an internal"
" helper tool called the \"preloader\", which"
" in turn had to be started through the operating"
" system (OS) shell. But the OS shell encountered "
+ categoryStringWithIndefiniteArticle,
category, advancedProblemDetails, stdoutAndErrData);
case SUBPROCESS_SPAWN_ENV_SETUPPER_BEFORE_SHELL:
case SUBPROCESS_SPAWN_ENV_SETUPPER_AFTER_SHELL:
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application. In doing so, "
SHORT_PROGRAM_NAME " had to first start an internal"
" helper tool called the \"preloader\", which"
" in turn had to be started through another internal"
" tool called the \"SpawnEnvSetupper\". But the"
" SpawnEnvSetupper encountered "
+ categoryStringWithIndefiniteArticle,
category, advancedProblemDetails, stdoutAndErrData);
case SUBPROCESS_EXEC_WRAPPER:
if (!config.genericApp && config.startsUsingWrapper && config.wrapperSuppliedByThirdParty) {
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application through a "
SHORT_PROGRAM_NAME " helper tool called"
" the \"wrapper\". This helper tool is not part of "
SHORT_PROGRAM_NAME ". But " SHORT_PROGRAM_NAME
" was unable to execute that helper tool"
" because it encountered "
+ categoryStringWithIndefiniteArticle,
category, advancedProblemDetails, stdoutAndErrData);
} else {
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application through a "
SHORT_PROGRAM_NAME "-internal helper tool called"
" the \"wrapper\". But " SHORT_PROGRAM_NAME
" was unable to execute that helper tool"
" because it encountered "
+ categoryStringWithIndefiniteArticle,
category, advancedProblemDetails, stdoutAndErrData);
}
case SUBPROCESS_WRAPPER_PREPARATION:
if (!config.genericApp && config.startsUsingWrapper && config.wrapperSuppliedByThirdParty) {
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application through a "
SHORT_PROGRAM_NAME " helper tool called"
" the \"wrapper\"). This tool is not part of "
SHORT_PROGRAM_NAME ". But that helper tool encountered "
+ categoryStringWithIndefiniteArticle,
category, advancedProblemDetails, stdoutAndErrData);
} else {
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application through a "
SHORT_PROGRAM_NAME "-internal helper tool called"
" the \"wrapper\"). But that helper tool encountered "
+ categoryStringWithIndefiniteArticle,
category, advancedProblemDetails, stdoutAndErrData);
}
case SUBPROCESS_APP_LOAD_OR_EXEC:
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application. But the application"
" itself (and not " SHORT_PROGRAM_NAME ") encountered "
+ categoryStringWithIndefiniteArticle,
category, advancedProblemDetails, stdoutAndErrData);
case SUBPROCESS_LISTEN:
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application. The application tried "
" to setup a socket for accepting connections,"
" but in doing so it encountered "
+ categoryStringWithIndefiniteArticle,
category, advancedProblemDetails, stdoutAndErrData);
case SUBPROCESS_FINISH:
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application, but the application"
" encountered "
+ categoryStringWithIndefiniteArticle
+ " while finalizing its startup procedure",
category, advancedProblemDetails, stdoutAndErrData);
default:
P_BUG("Unsupported preloader journey step "
<< toString((int) journey.getFirstFailedStep()));
}
default:
switch (journey.getFirstFailedStep()) {
case SPAWNING_KIT_PREPARATION:
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application, but " SHORT_PROGRAM_NAME
" encountered " + categoryStringWithIndefiniteArticle
+ " while performing preparation work",
category, advancedProblemDetails, stdoutAndErrData);
case SPAWNING_KIT_FORK_SUBPROCESS:
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application. But " SHORT_PROGRAM_NAME
" encountered " + categoryStringWithIndefiniteArticle
+ " while creating a subprocess",
category, advancedProblemDetails, stdoutAndErrData);
case SPAWNING_KIT_CONNECT_TO_PRELOADER:
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application by communicating with a"
" helper process that we call a \"preloader\". However, "
SHORT_PROGRAM_NAME " encountered "
+ categoryStringWithIndefiniteArticle
+ " while connecting to this helper process",
category, advancedProblemDetails, stdoutAndErrData);
case SPAWNING_KIT_SEND_COMMAND_TO_PRELOADER:
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application by communicating with a"
" helper process that we call a \"preloader\". However, "
SHORT_PROGRAM_NAME " encountered "
+ categoryStringWithIndefiniteArticle
+ " while sending a command to this helper process",
category, advancedProblemDetails, stdoutAndErrData);
case SPAWNING_KIT_READ_RESPONSE_FROM_PRELOADER:
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application by communicating with a"
" helper process that we call a \"preloader\". However, "
SHORT_PROGRAM_NAME " encountered "
+ categoryStringWithIndefiniteArticle
+ " while receiving a response to this helper process",
category, advancedProblemDetails, stdoutAndErrData);
case SPAWNING_KIT_PARSE_RESPONSE_FROM_PRELOADER:
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application by communicating with a"
" helper process that we call a \"preloader\". However, "
SHORT_PROGRAM_NAME " encountered "
+ categoryStringWithIndefiniteArticle
+ " while parsing a response from this helper process",
category, advancedProblemDetails, stdoutAndErrData);
case SPAWNING_KIT_PROCESS_RESPONSE_FROM_PRELOADER:
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application by communicating with a"
" helper process that we call a \"preloader\". However, "
SHORT_PROGRAM_NAME " encountered "
+ categoryStringWithIndefiniteArticle
+ " while processing a response from this helper process",
category, advancedProblemDetails, stdoutAndErrData);
case SPAWNING_KIT_HANDSHAKE_PERFORM:
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application. Everything was looking OK,"
" but then suddenly " SHORT_PROGRAM_NAME " encountered "
+ categoryStringWithIndefiniteArticle,
category, advancedProblemDetails, stdoutAndErrData);
case SUBPROCESS_BEFORE_FIRST_EXEC:
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application. " SHORT_PROGRAM_NAME
" launched a subprocess which was supposed to"
" execute the application, but instead that"
" subprocess encountered "
+ categoryStringWithIndefiniteArticle,
category, advancedProblemDetails, stdoutAndErrData);
case SUBPROCESS_OS_SHELL:
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application through the operating"
" system (OS) shell. But the OS shell encountered "
+ categoryStringWithIndefiniteArticle,
category, advancedProblemDetails, stdoutAndErrData);
case SUBPROCESS_SPAWN_ENV_SETUPPER_BEFORE_SHELL:
case SUBPROCESS_SPAWN_ENV_SETUPPER_AFTER_SHELL:
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application through a "
SHORT_PROGRAM_NAME "-internal helper tool called the"
" SpawnEnvSetupper. But that helper tool encountered "
+ categoryStringWithIndefiniteArticle,
category, advancedProblemDetails, stdoutAndErrData);
case SUBPROCESS_EXEC_WRAPPER:
if (!config.genericApp && config.startsUsingWrapper && config.wrapperSuppliedByThirdParty) {
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application through a "
SHORT_PROGRAM_NAME " helper tool called"
" the \"wrapper\". This helper tool is not part of "
SHORT_PROGRAM_NAME ". But " SHORT_PROGRAM_NAME
" was unable to execute that helper tool because"
" it encountered "
+ categoryStringWithIndefiniteArticle,
category, advancedProblemDetails, stdoutAndErrData);
} else {
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application through a "
SHORT_PROGRAM_NAME "-internal helper tool called"
" the \"wrapper\". But " SHORT_PROGRAM_NAME
" was unable to execute that helper tool because"
" it encountered "
+ categoryStringWithIndefiniteArticle,
category, advancedProblemDetails, stdoutAndErrData);
}
case SUBPROCESS_WRAPPER_PREPARATION:
if (!config.genericApp && config.startsUsingWrapper && config.wrapperSuppliedByThirdParty) {
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application through a "
SHORT_PROGRAM_NAME " helper tool called"
" the \"wrapper\". This helper tool is not part of "
SHORT_PROGRAM_NAME ". But that helper tool encountered "
+ categoryStringWithIndefiniteArticle,
category, advancedProblemDetails, stdoutAndErrData);
} else {
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application through a "
SHORT_PROGRAM_NAME "-internal helper tool called"
" the \"wrapper\". But that helper tool encountered "
+ categoryStringWithIndefiniteArticle,
category, advancedProblemDetails, stdoutAndErrData);
}
case SUBPROCESS_APP_LOAD_OR_EXEC:
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application. But the application"
" itself (and not " SHORT_PROGRAM_NAME ") encountered "
+ categoryStringWithIndefiniteArticle,
category, advancedProblemDetails, stdoutAndErrData);
case SUBPROCESS_PREPARE_AFTER_FORKING_FROM_PRELOADER:
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application through a "
SHORT_PROGRAM_NAME "-internal helper tool called"
" the \"preloader\". But the preloader encountered "
+ categoryStringWithIndefiniteArticle,
category, advancedProblemDetails, stdoutAndErrData);
case SUBPROCESS_LISTEN:
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application. The application tried "
" to setup a socket for accepting connections,"
" but in doing so it encountered "
+ categoryStringWithIndefiniteArticle,
category, advancedProblemDetails, stdoutAndErrData);
case SUBPROCESS_FINISH:
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried to"
" start the web application, but the application"
" encountered "
+ categoryStringWithIndefiniteArticle
+ " while finalizing its startup procedure",
category, advancedProblemDetails, stdoutAndErrData);
default:
P_BUG("Unrecognized journey step " <<
toString((int) journey.getFirstFailedStep()));
}
}
case TIMEOUT_ERROR:
// We only return a single error message instead of a customized
// one based on the failed step, because the timeout
// applies to the entire journey, not just to a specific step.
// A timeout at a specific step could be the result of a previous
// step taking too much time.
// The way to debug a timeout error is by looking at the timings
// of each step.
return wrapInParaAndMaybeAddErrorMessages(
"The " PROGRAM_NAME " application server tried"
" to start the web application, but this took too much time,"
" so " SHORT_PROGRAM_NAME " put a stop to that",
TIMEOUT_ERROR, string(), stdoutAndErrData);
default:
P_BUG("Unrecognized error category " + toString((int) category));
return string(); // Never reached, shut up compiler warning.
}
}
static string createDefaultSolutionDescription(ErrorCategory category,
const Journey &journey, const Config &config)
{
string message;
switch (category) {
case INTERNAL_ERROR:
return "<p class=\"sole-solution\">"
"Unfortunately, " SHORT_PROGRAM_NAME " does not know"
" how to solve this problem. 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>";
case FILE_SYSTEM_ERROR:
return "<p class=\"sole-solution\">"
"Unfortunately, " SHORT_PROGRAM_NAME " does not know how to"
" solve this problem. But it looks like some kind of filesystem error."
" This generally means that you need to fix nonexistant"
" files/directories or fix filesystem permissions. Please"
" try troubleshooting the problem by studying the"
" <strong>error message</strong> and the"
" <strong>diagnostics</strong> reports.</p>";
case OPERATING_SYSTEM_ERROR:
case IO_ERROR:
return "<div class=\"multiple-solutions\">"
"<h3>Check whether the server is low on resources</h3>"
"<p>Maybe the server is currently low on resources. This would"
" cause errors to occur. 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 your (filesystem) security settings</h3>"
"<p>Maybe security settings are preventing " SHORT_PROGRAM_NAME
" from doing the work it needs to do. Please check whether the"
" error may be caused by your system's security settings, or"
" whether it may be caused by wrong permissions on a file or"
" directory.</p>"
"<h3>Still no luck?</h3>"
"<p>Please try troubleshooting the problem by studying the"
" <em>diagnostics</em> reports.</p>"
"</div>";
case TIMEOUT_ERROR:
message = "<div class=\"multiple-solutions\">"
"<h3>Check whether the server is low on resources</h3>"
"<p>Maybe the server is currently so low on resources that"
" all the work that needed to be done, could not finish within"
" the given time limit."
" Please inspect the server resource utilization statistics"
" in the <em>diagnostics</em> section to verify"
" whether server is indeed low on resources.</p>"
"<p>If so, then either increase the spawn timeout (currently"
" configured at " + toString(config.startTimeoutMsec / 1000)
+ " sec), or find a way to lower the server's resource"
" utilization.</p>";
switch (journey.getFirstFailedStep()) {
case SUBPROCESS_OS_SHELL:
message.append(
"<h3>Check whether your OS shell's startup scripts can"
" take a long time or get stuck</h3>"
"<p>One of your OS shell's startup scripts may do too much work,"
" or it may have invoked a command that then got stuck."
" Please investigate and debug your OS shell's startup"
" scripts.</p>");
break;
case SUBPROCESS_APP_LOAD_OR_EXEC:
if (config.appType == "nodejs") {
message.append(
"<h3>Check whether the application calls <code>http.Server.listen()</code></h3>"
"<p>" SHORT_PROGRAM_NAME " requires that the application calls"
" <code>listen()</code> on an http.Server object. If"
" the application never calls this, then "
SHORT_PROGRAM_NAME " will think the application is"
" stuck. <a href=\"https://www.phusionpassenger.com/"
"library/indepth/nodejs/reverse_port_binding.html\">"
"Learn more about this problem.</a></p>");
}
message.append(
"<h3>Check whether the application is stuck during startup</h3>"
"<p>The easiest way find out where the application is stuck"
"is by inserting print statements into the application's code.</p>");
break;
default:
break;
}
message.append("<h3>Still no luck?</h3>"
"<p>Please try troubleshooting the problem by studying the"
" <em>diagnostics</em> reports.</p>"
"</div>");
return message;
default:
return "(error generating solution description: unknown error category)";
}
}
static string createDefaultAdvancedProblemDetails(const std::exception &e) {
const oxt::tracable_exception *te = dynamic_cast<const oxt::tracable_exception *>(&e);
if (te != NULL) {
return string(e.what()) + "\n" + te->backtrace();
} else {
return e.what();
}
}
static StaticString getErrorCategoryPhraseWithIndefiniteArticle(
ErrorCategory category, bool beginOfSentence)
{
switch (category) {
case INTERNAL_ERROR:
if (beginOfSentence) {
return P_STATIC_STRING("An internal error");
} else {
return P_STATIC_STRING("an internal error");
}
case FILE_SYSTEM_ERROR:
if (beginOfSentence) {
return P_STATIC_STRING("A file system error");
} else {
return P_STATIC_STRING("a file system error");
}
case OPERATING_SYSTEM_ERROR:
if (beginOfSentence) {
return P_STATIC_STRING("An operating system error");
} else {
return P_STATIC_STRING("an operating system error");
}
case IO_ERROR:
if (beginOfSentence) {
return P_STATIC_STRING("An I/O error");
} else {
return P_STATIC_STRING("an I/O error");
}
case TIMEOUT_ERROR:
if (beginOfSentence) {
return P_STATIC_STRING("A timeout error");
} else {
return P_STATIC_STRING("a timeout error");
}
default:
P_BUG("Unsupported error category " + toString((int) category));
return StaticString();
}
}
static StaticString getErrorCategoryPhraseWithIndefiniteArticle(
const std::exception &e, const Journey &journey,
bool beginOfSentence)
{
ErrorCategory category =
inferErrorCategoryFromAnotherException(
e, journey.getFirstFailedStep());
return getErrorCategoryPhraseWithIndefiniteArticle(category, beginOfSentence);
}
static string wrapInParaAndMaybeAddErrorMessages(const StaticString &message,
ErrorCategory category, const StaticString &advancedProblemDetails,
const StaticString &stdoutAndErrData)
{
string result = "<p>" + message + ".</p>";
if (!advancedProblemDetails.empty()) {
if (category == INTERNAL_ERROR || category == FILE_SYSTEM_ERROR) {
result.append("<p>Error details:</p>"
"<pre>" + escapeHTML(advancedProblemDetails) + "</pre>");
} else if (category == IO_ERROR) {
result.append("<p>The error reported by the I/O layer is:</p>"
"<pre>" + escapeHTML(advancedProblemDetails) + "</pre>");
} else {
P_ASSERT_EQ(category, OPERATING_SYSTEM_ERROR);
result.append("<p>The error reported by the operating system is:</p>"
"<pre>" + escapeHTML(advancedProblemDetails) + "</pre>");
}
}
if (!stdoutAndErrData.empty()) {
result.append("<p>The stdout/stderr output of the subprocess so far is:</p>"
"<pre>" + escapeHTML(stdoutAndErrData) + "</pre>");
}
return result;
}
static string gatherEnvvars() {
string result;
unsigned int i = 0;
while (environ[i] != NULL) {
result.append(environ[i]);
result.append(1, '\n');
i++;
}
return result;
}
static string gatherUlimits() {
// On Linux, ulimit is a shell builtin and not a command.
const char *command[] = { "/bin/sh", "-c", "ulimit -a", NULL };
try {
SubprocessInfo info;
SubprocessOutput output;
runCommandAndCaptureOutput(command, info, output,
std::numeric_limits<size_t>::max());
if (output.data.empty()) {
output.data.assign("Error: command 'ulimit -a' failed");
}
return output.data;
} catch (const SystemException &e) {
return P_STATIC_STRING("Error: command 'ulimit -a' failed: ") + e.what();
}
}
static string gatherUserInfo() {
const char *command[] = { "id", "-a", NULL };
try {
SubprocessInfo info;
SubprocessOutput output;
runCommandAndCaptureOutput(command, info, output,
std::numeric_limits<size_t>::max());
if (output.data.empty()) {
output.data.assign("Error: command 'id -a' failed");
}
return output.data;
} catch (const SystemException &e) {
return P_STATIC_STRING("Error: command 'id -a' failed: ") + e.what();
}
}
static string gatherSystemMetrics() {
SystemMetrics metrics;
try {
SystemMetricsCollector().collect(metrics);
} catch (const RuntimeException &e) {
return "Error: cannot parse system metrics: " + StaticString(e.what());
}
FastStringStream<> stream;
metrics.toDescription(stream);
return string(stream.data(), stream.size());
}
public:
SpawnException(ErrorCategory _category, const Journey &_journey,
const Config *_config)
: category(_category),
journey(_journey),
config(*_config)
{
assert(_journey.getFirstFailedStep() != UNKNOWN_JOURNEY_STEP);
config.internStrings();
}
SpawnException(const std::exception &originalException,
const Journey &_journey, const Config *_config)
: category(inferErrorCategoryFromAnotherException(
originalException, _journey.getFirstFailedStep())),
journey(_journey),
config(*_config),
summary(createDefaultSummary(
category, _journey, originalException.what())),
advancedProblemDetails(createDefaultAdvancedProblemDetails(originalException))
{
assert(_journey.getFirstFailedStep() != UNKNOWN_JOURNEY_STEP);
config.internStrings();
}
virtual ~SpawnException() throw() {}
virtual const char *what() const throw() {
return summary.c_str();
}
ErrorCategory getErrorCategory() const {
return category;
}
const Journey &getJourney() const {
return journey;
}
const Config &getConfig() const {
return config;
}
const string &getSummary() const {
return summary;
}
void setSummary(const string &value) {
summary = value;
}
const string &getAdvancedProblemDetails() const {
return advancedProblemDetails;
}
void setAdvancedProblemDetails(const string &value) {
advancedProblemDetails = value;
}
const string &getProblemDescriptionHTML() const {
return problemDescription;
}
void setProblemDescriptionHTML(const string &value) {
problemDescription = value;
}
const string &getSolutionDescriptionHTML() const {
return solutionDescription;
}
void setSolutionDescriptionHTML(const string &value) {
solutionDescription = value;
}
const string &getStdoutAndErrData() const {
return stdoutAndErrData;
}
void setStdoutAndErrData(const string &value) {
stdoutAndErrData = value;
}
const string &getId() const {
return id;
}
void setId(const string &value) {
id = value;
}
SpawnException &finalize() {
TRACE_POINT();
if (summary.empty()) {
summary = createDefaultSummary(category, journey,
advancedProblemDetails);
}
if (problemDescription.empty()) {
problemDescription = createDefaultProblemDescription(
category, journey, config, advancedProblemDetails,
stdoutAndErrData);
}
if (solutionDescription.empty()) {
solutionDescription = createDefaultSolutionDescription(
category, journey, config);
}
parentProcessEnvDump.pid = getpid();
parentProcessEnvDump.envvars = gatherEnvvars();
parentProcessEnvDump.userInfo = gatherUserInfo();
parentProcessEnvDump.ulimits = gatherUlimits();
systemMetrics = gatherSystemMetrics();
return *this;
}
const string &getParentProcessEnvvars() const {
return parentProcessEnvDump.envvars;
}
const string &getParentProcessUserInfo() const {
return parentProcessEnvDump.userInfo;
}
const string &getParentProcessUlimits() const {
return parentProcessEnvDump.ulimits;
}
pid_t getPreloaderPid() const {
return preloaderEnvDump.pid;
}
void setPreloaderPid(pid_t pid) {
preloaderEnvDump.pid = pid;
}
const string &getPreloaderEnvvars() const {
return preloaderEnvDump.envvars;
}
void setPreloaderEnvvars(const string &value) {
preloaderEnvDump.envvars = value;
}
const string &getPreloaderUserInfo() const {
return preloaderEnvDump.userInfo;
}
void setPreloaderUserInfo(const string &value) {
preloaderEnvDump.userInfo = value;
}
const string &getPreloaderUlimits() const {
return preloaderEnvDump.ulimits;
}
void setPreloaderUlimits(const string &value) {
preloaderEnvDump.ulimits = value;
}
pid_t getSubprocessPid() const {
return subprocessEnvDump.pid;
}
void setSubprocessPid(pid_t pid) {
subprocessEnvDump.pid = pid;
}
const string &getSubprocessEnvvars() const {
return subprocessEnvDump.envvars;
}
void setSubprocessEnvvars(const string &value) {
subprocessEnvDump.envvars = value;
}
const string &getSubprocessUserInfo() const {
return subprocessEnvDump.userInfo;
}
void setSubprocessUserInfo(const string &value) {
subprocessEnvDump.userInfo = value;
}
const string &getSubprocessUlimits() const {
return subprocessEnvDump.ulimits;
}
void setSubprocessUlimits(const string &value) {
subprocessEnvDump.ulimits = value;
}
const string &getSystemMetrics() const {
return systemMetrics;
}
const StringKeyTable<string> &getAnnotations() const {
return annotations;
}
void setAnnotation(const HashedStaticString &name, const string &value,
bool overwrite = true)
{
annotations.insert(name, value, overwrite);
}
Json::Value inspectBasicInfoAsJson() const {
Json::Value doc;
doc["category"] = errorCategoryToString(category).toString();
doc["summary"] = summary;
doc["problem_description_html"] = problemDescription;
doc["solution_description_html"] = solutionDescription;
if (!advancedProblemDetails.empty()) {
doc["aux_details"] = advancedProblemDetails;
}
if (!id.empty()) {
doc["id"] = id;
}
return doc;
}
Json::Value inspectSystemWideDetailsAsJson() const {
Json::Value doc;
doc["system_metrics"] = systemMetrics;
return doc;
}
Json::Value inspectParentProcessDetailsAsJson() const {
Json::Value doc;
doc["backtrace"] = backtrace();
doc["pid"] = (Json::Int) parentProcessEnvDump.pid;
doc["envvars"] = getParentProcessEnvvars();
doc["user_info"] = getParentProcessUserInfo();
doc["ulimits"] = getParentProcessUlimits();
return doc;
}
Json::Value inspectPreloaderProcessDetailsAsJson() const {
Json::Value doc, annotations(Json::objectValue);
if (getPreloaderPid() != (pid_t) -1) {
doc["pid"] = getPreloaderPid();
}
doc["envvars"] = getPreloaderEnvvars();
doc["user_info"] = getPreloaderUserInfo();
doc["ulimits"] = getPreloaderUlimits();
StringKeyTable<string>::ConstIterator it(this->annotations);
while (*it != NULL) {
annotations[it.getKey().toString()] = it.getValue();
it.next();
}
doc["annotations"] = annotations;
return doc;
}
Json::Value inspectSubprocessDetailsAsJson() const {
Json::Value doc, annotations(Json::objectValue);
if (getSubprocessPid() != (pid_t) -1) {
doc["pid"] = getSubprocessPid();
}
doc["envvars"] = getSubprocessEnvvars();
doc["user_info"] = getSubprocessUserInfo();
doc["ulimits"] = getSubprocessUlimits();
doc["stdout_and_err"] = getStdoutAndErrData();
StringKeyTable<string>::ConstIterator it(this->annotations);
while (*it != NULL) {
annotations[it.getKey().toString()] = it.getValue();
it.next();
}
doc["annotations"] = annotations;
return doc;
}
};
inline StaticString
errorCategoryToString(ErrorCategory category) {
switch (category) {
case INTERNAL_ERROR:
return P_STATIC_STRING("INTERNAL_ERROR");
case FILE_SYSTEM_ERROR:
return P_STATIC_STRING("FILE_SYSTEM_ERROR");
case OPERATING_SYSTEM_ERROR:
return P_STATIC_STRING("OPERATING_SYSTEM_ERROR");
case IO_ERROR:
return P_STATIC_STRING("IO_ERROR");
case TIMEOUT_ERROR:
return P_STATIC_STRING("TIMEOUT_ERROR");
case UNKNOWN_ERROR_CATEGORY:
return P_STATIC_STRING("UNKNOWN_ERROR_CATEGORY");
default:
return P_STATIC_STRING("(invalid value)");
}
}
inline bool
_isFileSystemError(const std::exception &e) {
if (dynamic_cast<const FileSystemException *>(&e) != NULL) {
return true;
}
const SystemException *sysEx = dynamic_cast<const SystemException *>(&e);
if (sysEx != NULL) {
return sysEx->code() == ENOENT
|| sysEx->code() == ENAMETOOLONG
|| sysEx->code() == EEXIST
|| sysEx->code() == EACCES;
}
return false;
}
inline bool
_systemErrorIsActuallyIoError(JourneyStep failedJourneyStep) {
return failedJourneyStep == SPAWNING_KIT_CONNECT_TO_PRELOADER
|| failedJourneyStep == SPAWNING_KIT_SEND_COMMAND_TO_PRELOADER
|| failedJourneyStep == SPAWNING_KIT_READ_RESPONSE_FROM_PRELOADER;
}
inline ErrorCategory
inferErrorCategoryFromAnotherException(const std::exception &e,
JourneyStep failedJourneyStep)
{
if (dynamic_cast<const SystemException *>(&e) != NULL) {
if (_systemErrorIsActuallyIoError(failedJourneyStep)) {
return IO_ERROR;
} else {
return OPERATING_SYSTEM_ERROR;
}
} else if (_isFileSystemError(e)) {
return FILE_SYSTEM_ERROR;
} else if (dynamic_cast<const IOException *>(&e) != NULL) {
return IO_ERROR;
} else if (dynamic_cast<const TimeoutException *>(&e) != NULL) {
return TIMEOUT_ERROR;
} else {
return INTERNAL_ERROR;
}
}
inline ErrorCategory
stringToErrorCategory(const StaticString &value) {
if (value == P_STATIC_STRING("INTERNAL_ERROR")) {
return INTERNAL_ERROR;
} else if (value == P_STATIC_STRING("FILE_SYSTEM_ERROR")) {
return FILE_SYSTEM_ERROR;
} else if (value == P_STATIC_STRING("OPERATING_SYSTEM_ERROR")) {
return OPERATING_SYSTEM_ERROR;
} else if (value == P_STATIC_STRING("IO_ERROR")) {
return IO_ERROR;
} else if (value == P_STATIC_STRING("TIMEOUT_ERROR")) {
return TIMEOUT_ERROR;
} else {
return UNKNOWN_ERROR_CATEGORY;
}
}
} // namespace SpawningKit
} // namespace Passenger
#endif /* _PASSENGER_SPAWNING_KIT_EXCEPTIONS_H_ */