passenger 4.0.2 → 4.0.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of passenger might be problematic. Click here for more details.
- data.tar.gz.asc +7 -7
- data/NEWS +27 -0
- data/bin/passenger-config +6 -3
- data/bin/passenger-install-apache2-module +2 -2
- data/bin/passenger-install-nginx-module +16 -2
- data/build/agents.rb +4 -0
- data/build/apache2.rb +1 -1
- data/build/cplusplus_support.rb +1 -1
- data/build/cxx_tests.rb +3 -0
- data/build/packaging.rb +51 -8
- data/build/ruby_extension.rb +1 -1
- data/doc/Packaging.txt.md +20 -7
- data/doc/Users guide Apache.html +1 -1
- data/doc/Users guide Apache.txt +1 -1
- data/doc/Users guide Nginx.html +5 -4
- data/doc/Users guide Nginx.txt +1 -1
- data/doc/users_guide_snippets/installation.txt +5 -3
- data/ext/apache2/Configuration.cpp +12 -0
- data/ext/apache2/Configuration.hpp +7 -4
- data/ext/apache2/Hooks.cpp +29 -19
- data/ext/common/AgentsStarter.cpp +85 -57
- data/ext/common/AgentsStarter.h +570 -42
- data/ext/common/ApplicationPool2/DirectSpawner.h +5 -2
- data/ext/common/ApplicationPool2/Implementation.cpp +7 -1
- data/ext/common/ApplicationPool2/Pool.h +6 -3
- data/ext/common/ApplicationPool2/Process.h +12 -3
- data/ext/common/ApplicationPool2/SmartSpawner.h +2 -1
- data/ext/common/Constants.h +4 -1
- data/ext/common/EventedBufferedInput.h +139 -16
- data/ext/common/MultiLibeio.cpp +4 -2
- data/ext/common/SafeLibev.h +15 -62
- data/ext/common/ServerInstanceDir.h +10 -26
- data/ext/common/Utils.cpp +1 -3
- data/ext/common/Utils.h +1 -1
- data/ext/common/Utils/StrIntUtils.cpp +9 -0
- data/ext/common/Utils/StrIntUtils.h +5 -0
- data/ext/common/Utils/VariantMap.h +63 -14
- data/ext/common/agents/Base.cpp +50 -15
- data/ext/common/agents/HelperAgent/AgentOptions.h +20 -12
- data/ext/common/agents/HelperAgent/FileBackedPipe.h +1 -1
- data/ext/common/agents/HelperAgent/Main.cpp +5 -4
- data/ext/common/agents/HelperAgent/RequestHandler.h +1 -1
- data/ext/common/agents/LoggingAgent/Main.cpp +0 -1
- data/ext/common/agents/LoggingAgent/RemoteSender.h +2 -2
- data/ext/common/agents/SpawnPreparer.cpp +23 -5
- data/ext/common/agents/Watchdog/AgentWatcher.cpp +508 -0
- data/ext/common/agents/Watchdog/HelperAgentWatcher.cpp +93 -0
- data/ext/common/agents/Watchdog/LoggingAgentWatcher.cpp +68 -0
- data/ext/common/agents/Watchdog/Main.cpp +180 -802
- data/ext/common/agents/Watchdog/ServerInstanceDirToucher.cpp +111 -0
- data/ext/nginx/Configuration.c +107 -92
- data/ext/nginx/Configuration.h +1 -0
- data/ext/nginx/ContentHandler.c +6 -6
- data/ext/nginx/ContentHandler.h +1 -1
- data/ext/nginx/config +8 -2
- data/ext/nginx/ngx_http_passenger_module.c +54 -60
- data/ext/nginx/ngx_http_passenger_module.h +6 -6
- data/lib/phusion_passenger.rb +17 -10
- data/lib/phusion_passenger/admin_tools/server_instance.rb +2 -2
- data/lib/phusion_passenger/common_library.rb +0 -1
- data/lib/phusion_passenger/platform_info.rb +10 -1
- data/lib/phusion_passenger/platform_info/depcheck.rb +4 -4
- data/lib/phusion_passenger/platform_info/depcheck_specs/compiler_toolchain.rb +2 -2
- data/lib/phusion_passenger/platform_info/ruby.rb +7 -0
- data/lib/phusion_passenger/request_handler.rb +119 -42
- data/lib/phusion_passenger/request_handler/thread_handler.rb +25 -22
- data/lib/phusion_passenger/standalone/command.rb +2 -0
- data/lib/phusion_passenger/standalone/runtime_installer.rb +4 -3
- data/lib/phusion_passenger/standalone/start_command.rb +49 -37
- data/resources/templates/nginx/pcre_checksum_could_not_be_verified.txt.erb +11 -0
- data/test/cxx/CxxTestMain.cpp +2 -0
- data/test/cxx/EventedBufferedInputTest.cpp +758 -0
- data/test/cxx/ServerInstanceDirTest.cpp +16 -31
- data/test/cxx/TestSupport.cpp +2 -1
- data/test/cxx/VariantMapTest.cpp +23 -11
- metadata +8 -4
- metadata.gz.asc +7 -7
- data/ext/common/AgentsStarter.hpp +0 -655
- data/lib/phusion_passenger/utils/robust_interruption.rb +0 -173
data/ext/common/agents/Base.cpp
CHANGED
@@ -30,6 +30,7 @@
|
|
30
30
|
#include <oxt/system_calls.hpp>
|
31
31
|
#include <oxt/backtrace.hpp>
|
32
32
|
#include <sys/types.h>
|
33
|
+
#include <sys/stat.h>
|
33
34
|
#include <sys/select.h>
|
34
35
|
#ifdef __linux__
|
35
36
|
#include <sys/syscall.h>
|
@@ -1426,6 +1427,37 @@ initializeSyscallFailureSimulation(const char *processName) {
|
|
1426
1427
|
}
|
1427
1428
|
}
|
1428
1429
|
|
1430
|
+
enum FdIsSocketResult {
|
1431
|
+
FISR_YES,
|
1432
|
+
FISR_NO,
|
1433
|
+
FISR_ERROR
|
1434
|
+
};
|
1435
|
+
|
1436
|
+
static FdIsSocketResult fdIsSocket(int fd) {
|
1437
|
+
int ret = fcntl(fd, F_GETFL);
|
1438
|
+
if (ret == -1) {
|
1439
|
+
if (errno == EBADF) {
|
1440
|
+
return FISR_NO;
|
1441
|
+
} else {
|
1442
|
+
return FISR_ERROR;
|
1443
|
+
}
|
1444
|
+
} else {
|
1445
|
+
struct stat buf;
|
1446
|
+
ret = fstat(fd, &buf);
|
1447
|
+
if (ret == -1) {
|
1448
|
+
// I think some platforms return this for anonymous
|
1449
|
+
// Unix socket pairs.
|
1450
|
+
return FISR_YES;
|
1451
|
+
} else {
|
1452
|
+
if (buf.st_mode & S_IFSOCK) {
|
1453
|
+
return FISR_YES;
|
1454
|
+
} else {
|
1455
|
+
return FISR_NO;
|
1456
|
+
}
|
1457
|
+
}
|
1458
|
+
}
|
1459
|
+
}
|
1460
|
+
|
1429
1461
|
VariantMap
|
1430
1462
|
initializeAgent(int argc, char *argv[], const char *processName) {
|
1431
1463
|
VariantMap options;
|
@@ -1460,27 +1492,30 @@ initializeAgent(int argc, char *argv[], const char *processName) {
|
|
1460
1492
|
TRACE_POINT();
|
1461
1493
|
try {
|
1462
1494
|
if (argc == 1) {
|
1463
|
-
int
|
1464
|
-
|
1465
|
-
|
1466
|
-
|
1467
|
-
"You're not supposed to start this program from the command line. "
|
1468
|
-
"It's used internally by Phusion Passenger.\n");
|
1469
|
-
exit(1);
|
1470
|
-
} else {
|
1471
|
-
int e = errno;
|
1472
|
-
fprintf(stderr,
|
1473
|
-
"Encountered an error in feedback file descriptor 3: %s (%d)\n",
|
1474
|
-
strerror(e), e);
|
1475
|
-
exit(1);
|
1476
|
-
}
|
1477
|
-
} else {
|
1495
|
+
int e;
|
1496
|
+
|
1497
|
+
switch (fdIsSocket(FEEDBACK_FD)) {
|
1498
|
+
case FISR_YES:
|
1478
1499
|
_feedbackFdAvailable = true;
|
1479
1500
|
options.readFrom(FEEDBACK_FD);
|
1480
1501
|
if (options.getBool("fire_and_forget", false)) {
|
1481
1502
|
_feedbackFdAvailable = false;
|
1482
1503
|
close(FEEDBACK_FD);
|
1483
1504
|
}
|
1505
|
+
break;
|
1506
|
+
case FISR_NO:
|
1507
|
+
fprintf(stderr,
|
1508
|
+
"You're not supposed to start this program from the command line. "
|
1509
|
+
"It's used internally by Phusion Passenger.\n");
|
1510
|
+
exit(1);
|
1511
|
+
break;
|
1512
|
+
case FISR_ERROR:
|
1513
|
+
e = errno;
|
1514
|
+
fprintf(stderr,
|
1515
|
+
"Encountered an error in feedback file descriptor 3: %s (%d)\n",
|
1516
|
+
strerror(e), e);
|
1517
|
+
exit(1);
|
1518
|
+
break;
|
1484
1519
|
}
|
1485
1520
|
} else {
|
1486
1521
|
options.readFrom((const char **) argv + 1, argc - 1);
|
@@ -28,7 +28,6 @@
|
|
28
28
|
#include <sys/types.h>
|
29
29
|
#include <string>
|
30
30
|
#include <Utils/VariantMap.h>
|
31
|
-
#include <Utils/Base64.h>
|
32
31
|
|
33
32
|
namespace Passenger {
|
34
33
|
|
@@ -37,6 +36,7 @@ using namespace std;
|
|
37
36
|
|
38
37
|
struct AgentOptions {
|
39
38
|
pid_t webServerPid;
|
39
|
+
string serverInstanceDir;
|
40
40
|
string tempDir;
|
41
41
|
bool userSwitching;
|
42
42
|
string defaultUser;
|
@@ -47,11 +47,13 @@ struct AgentOptions {
|
|
47
47
|
unsigned int maxPoolSize;
|
48
48
|
unsigned int maxInstancesPerApp;
|
49
49
|
unsigned int poolIdleTime;
|
50
|
+
string requestSocketFilename;
|
50
51
|
string requestSocketPassword;
|
51
|
-
string
|
52
|
+
string adminSocketAddress;
|
53
|
+
string exitPassword;
|
52
54
|
string loggingAgentAddress;
|
53
55
|
string loggingAgentPassword;
|
54
|
-
string prestartUrls;
|
56
|
+
vector<string> prestartUrls;
|
55
57
|
|
56
58
|
string requestSocketLink;
|
57
59
|
|
@@ -59,26 +61,32 @@ struct AgentOptions {
|
|
59
61
|
|
60
62
|
AgentOptions(const VariantMap &options) {
|
61
63
|
// Required options for which a default is already set by the Watchdog.
|
62
|
-
passengerRoot
|
63
|
-
tempDir
|
64
|
-
userSwitching
|
65
|
-
defaultRubyCommand
|
66
|
-
defaultUser
|
67
|
-
defaultGroup
|
64
|
+
passengerRoot = options.get("passenger_root");
|
65
|
+
tempDir = options.get("temp_dir");
|
66
|
+
userSwitching = options.getBool("user_switching");
|
67
|
+
defaultRubyCommand = options.get("default_ruby");
|
68
|
+
defaultUser = options.get("default_user");
|
69
|
+
defaultGroup = options.get("default_group");
|
68
70
|
maxPoolSize = options.getInt("max_pool_size");
|
69
71
|
maxInstancesPerApp = options.getInt("max_instances_per_app");
|
70
72
|
poolIdleTime = options.getInt("pool_idle_time");
|
71
73
|
|
72
74
|
// Required options only set by the Watchdog.
|
73
75
|
webServerPid = options.getPid("web_server_pid");
|
76
|
+
serverInstanceDir = options.get("server_instance_dir");
|
74
77
|
generationNumber = options.getInt("generation_number");
|
75
|
-
|
76
|
-
|
78
|
+
requestSocketFilename = options.get("request_socket_filename");
|
79
|
+
requestSocketPassword = options.get("request_socket_password");
|
80
|
+
if (requestSocketPassword == "-") {
|
81
|
+
requestSocketPassword = "";
|
82
|
+
}
|
83
|
+
adminSocketAddress = options.get("helper_agent_admin_socket_address");
|
84
|
+
exitPassword = options.get("helper_agent_exit_password");
|
77
85
|
loggingAgentAddress = options.get("logging_agent_address");
|
78
86
|
loggingAgentPassword = options.get("logging_agent_password");
|
79
87
|
|
80
88
|
// Optional options.
|
81
|
-
prestartUrls = options.
|
89
|
+
prestartUrls = options.getStrSet("prestart_urls", false);
|
82
90
|
requestSocketLink = options.get("request_socket_link", false);
|
83
91
|
}
|
84
92
|
};
|
@@ -422,7 +422,7 @@ private:
|
|
422
422
|
if (pthread_equal(pthread_self(), getLibev()->getCurrentThread())) {
|
423
423
|
real_dataConsumed(consumed, done, oldGeneration);
|
424
424
|
} else {
|
425
|
-
getLibev()->
|
425
|
+
getLibev()->runLater(boost::bind(
|
426
426
|
&FileBackedPipe::real_dataConsumed, this,
|
427
427
|
consumed, done, oldGeneration));
|
428
428
|
}
|
@@ -372,7 +372,7 @@ public:
|
|
372
372
|
Server(FileDescriptor feedbackFd, const AgentOptions &_options)
|
373
373
|
: options(_options),
|
374
374
|
requestLoop(true),
|
375
|
-
serverInstanceDir(
|
375
|
+
serverInstanceDir(_options.serverInstanceDir, false),
|
376
376
|
resourceLocator(options.passengerRoot)
|
377
377
|
{
|
378
378
|
TRACE_POINT();
|
@@ -383,8 +383,9 @@ public:
|
|
383
383
|
startListening();
|
384
384
|
accountsDatabase = AccountsDatabase::createDefault(generation,
|
385
385
|
options.userSwitching, options.defaultUser, options.defaultGroup);
|
386
|
-
accountsDatabase->add("_web_server", options.
|
387
|
-
messageServer =
|
386
|
+
accountsDatabase->add("_web_server", options.exitPassword, false, Account::EXIT);
|
387
|
+
messageServer = make_shared<MessageServer>(
|
388
|
+
parseUnixSocketAddress(options.adminSocketAddress), accountsDatabase);
|
388
389
|
|
389
390
|
createFile(generation->getPath() + "/helper_agent.pid",
|
390
391
|
toString(getpid()), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
@@ -530,7 +531,7 @@ public:
|
|
530
531
|
}
|
531
532
|
|
532
533
|
string getRequestSocketFilename() const {
|
533
|
-
return
|
534
|
+
return options.requestSocketFilename;
|
534
535
|
}
|
535
536
|
};
|
536
537
|
|
@@ -1860,7 +1860,7 @@ private:
|
|
1860
1860
|
|
1861
1861
|
void sessionCheckedOut(ClientPtr client, const SessionPtr &session, const ExceptionPtr &e) {
|
1862
1862
|
if (!pthread_equal(pthread_self(), libev->getCurrentThread())) {
|
1863
|
-
libev->
|
1863
|
+
libev->runLater(boost::bind(&RequestHandler::sessionCheckedOut_real, this,
|
1864
1864
|
client, session, e));
|
1865
1865
|
} else {
|
1866
1866
|
sessionCheckedOut_real(client, session, e);
|
@@ -543,8 +543,8 @@ public:
|
|
543
543
|
}
|
544
544
|
stream << "\n";
|
545
545
|
stream << " Items in queue: " << queue.size() << "\n";
|
546
|
-
stream << "
|
547
|
-
stream << "
|
546
|
+
stream << " Packets sent out so far: " << packetsSent << "\n";
|
547
|
+
stream << " Packets dropped out so far: " << packetsDropped << "\n";
|
548
548
|
stream << " Next server checkup time: ";
|
549
549
|
if (nextCheckupTime == 0) {
|
550
550
|
stream << "not yet scheduled, waiting for first packet\n";
|
@@ -24,6 +24,22 @@ extern "C" {
|
|
24
24
|
extern char **environ;
|
25
25
|
}
|
26
26
|
|
27
|
+
static void
|
28
|
+
changeWorkingDir(const char *dir) {
|
29
|
+
int ret = chdir(dir);
|
30
|
+
if (ret == 0) {
|
31
|
+
setenv("PWD", dir, 1);
|
32
|
+
} else {
|
33
|
+
int e = errno;
|
34
|
+
printf("!> Error\n");
|
35
|
+
printf("!> \n");
|
36
|
+
printf("Unable to change working directory to '%s': %s (errno=%d)\n",
|
37
|
+
dir, strerror(e), e);
|
38
|
+
fflush(stdout);
|
39
|
+
exit(1);
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
27
43
|
static void
|
28
44
|
setGivenEnvVars(const char *envvarsData) {
|
29
45
|
string envvars = Base64::decode(envvarsData);
|
@@ -144,18 +160,20 @@ dumpInformation() {
|
|
144
160
|
#endif
|
145
161
|
}
|
146
162
|
|
147
|
-
// Usage: SpawnPreparer <envvars> <executable> <exec args...>
|
163
|
+
// Usage: SpawnPreparer <working directory> <envvars> <executable> <exec args...>
|
148
164
|
int
|
149
165
|
main(int argc, char *argv[]) {
|
150
|
-
if (argc <
|
166
|
+
if (argc < 5) {
|
151
167
|
fprintf(stderr, "Too few arguments.\n");
|
152
168
|
exit(1);
|
153
169
|
}
|
154
170
|
|
155
|
-
const char *
|
156
|
-
const char *
|
157
|
-
char
|
171
|
+
const char *workingDir = argv[1];
|
172
|
+
const char *envvars = argv[2];
|
173
|
+
const char *executable = argv[3];
|
174
|
+
char **execArgs = &argv[4];
|
158
175
|
|
176
|
+
changeWorkingDir(workingDir);
|
159
177
|
setGivenEnvVars(envvars);
|
160
178
|
dumpInformation();
|
161
179
|
|
@@ -0,0 +1,508 @@
|
|
1
|
+
/*
|
2
|
+
* Phusion Passenger - https://www.phusionpassenger.com/
|
3
|
+
* Copyright (c) 2010-2013 Phusion
|
4
|
+
*
|
5
|
+
* "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
|
6
|
+
*
|
7
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
* of this software and associated documentation files (the "Software"), to deal
|
9
|
+
* in the Software without restriction, including without limitation the rights
|
10
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is
|
12
|
+
* furnished to do so, subject to the following conditions:
|
13
|
+
*
|
14
|
+
* The above copyright notice and this permission notice shall be included in
|
15
|
+
* all copies or substantial portions of the Software.
|
16
|
+
*
|
17
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
23
|
+
* THE SOFTWARE.
|
24
|
+
*/
|
25
|
+
|
26
|
+
/**
|
27
|
+
* Abstract base class for watching agent processes.
|
28
|
+
*/
|
29
|
+
class AgentWatcher: public enable_shared_from_this<AgentWatcher> {
|
30
|
+
private:
|
31
|
+
/** The watcher thread. */
|
32
|
+
oxt::thread *thr;
|
33
|
+
|
34
|
+
void threadMain(shared_ptr<AgentWatcher> self) {
|
35
|
+
try {
|
36
|
+
pid_t pid, ret;
|
37
|
+
int status, e;
|
38
|
+
|
39
|
+
while (!this_thread::interruption_requested()) {
|
40
|
+
{
|
41
|
+
lock_guard<boost::mutex> l(lock);
|
42
|
+
pid = this->pid;
|
43
|
+
}
|
44
|
+
|
45
|
+
// Process can be started before the watcher thread is launched.
|
46
|
+
if (pid == 0) {
|
47
|
+
pid = start();
|
48
|
+
}
|
49
|
+
ret = syscalls::waitpid(pid, &status, 0);
|
50
|
+
if (ret == -1 && errno == ECHILD) {
|
51
|
+
/* If the agent is attached to gdb then waitpid()
|
52
|
+
* here can return -1 with errno == ECHILD.
|
53
|
+
* Fallback to kill() polling for checking
|
54
|
+
* whether the agent is alive.
|
55
|
+
*/
|
56
|
+
ret = pid;
|
57
|
+
status = 0;
|
58
|
+
P_WARN("waitpid() on " << name() << " (pid=" << pid <<
|
59
|
+
") returned -1 with " <<
|
60
|
+
"errno = ECHILD, falling back to kill polling");
|
61
|
+
waitpidUsingKillPolling(pid);
|
62
|
+
e = 0;
|
63
|
+
} else {
|
64
|
+
e = errno;
|
65
|
+
}
|
66
|
+
|
67
|
+
{
|
68
|
+
lock_guard<boost::mutex> l(lock);
|
69
|
+
this->pid = 0;
|
70
|
+
}
|
71
|
+
|
72
|
+
this_thread::disable_interruption di;
|
73
|
+
this_thread::disable_syscall_interruption dsi;
|
74
|
+
if (ret == -1) {
|
75
|
+
P_WARN(name() << " (pid=" << pid << ") crashed or killed for "
|
76
|
+
"an unknown reason (errno = " <<
|
77
|
+
strerror(e) << "), restarting it...");
|
78
|
+
} else if (WIFEXITED(status)) {
|
79
|
+
if (WEXITSTATUS(status) == 0) {
|
80
|
+
/* When the web server is gracefully exiting, it will
|
81
|
+
* tell one or more agents to gracefully exit with exit
|
82
|
+
* status 0. If we see this then it means the watchdog
|
83
|
+
* is gracefully shutting down too and we should stop
|
84
|
+
* watching.
|
85
|
+
*/
|
86
|
+
return;
|
87
|
+
} else {
|
88
|
+
P_WARN(name() << " (pid=" << pid <<
|
89
|
+
") crashed with exit status " <<
|
90
|
+
WEXITSTATUS(status) << ", restarting it...");
|
91
|
+
}
|
92
|
+
} else {
|
93
|
+
P_WARN(name() << " (pid=" << pid <<
|
94
|
+
") crashed with signal " <<
|
95
|
+
getSignalName(WTERMSIG(status)) <<
|
96
|
+
", restarting it...");
|
97
|
+
}
|
98
|
+
|
99
|
+
const char *sleepTime;
|
100
|
+
if ((sleepTime = getenv("PASSENGER_AGENT_RESTART_SLEEP")) != NULL) {
|
101
|
+
sleep(atoi(sleepTime));
|
102
|
+
}
|
103
|
+
}
|
104
|
+
} catch (const boost::thread_interrupted &) {
|
105
|
+
} catch (const tracable_exception &e) {
|
106
|
+
lock_guard<boost::mutex> l(lock);
|
107
|
+
threadExceptionMessage = e.what();
|
108
|
+
threadExceptionBacktrace = e.backtrace();
|
109
|
+
errorEvent->notify();
|
110
|
+
} catch (const std::exception &e) {
|
111
|
+
lock_guard<boost::mutex> l(lock);
|
112
|
+
threadExceptionMessage = e.what();
|
113
|
+
errorEvent->notify();
|
114
|
+
} catch (...) {
|
115
|
+
lock_guard<boost::mutex> l(lock);
|
116
|
+
threadExceptionMessage = "Unknown error";
|
117
|
+
errorEvent->notify();
|
118
|
+
}
|
119
|
+
}
|
120
|
+
|
121
|
+
protected:
|
122
|
+
/** PID of the process we're watching. 0 if no process is started at this time. */
|
123
|
+
pid_t pid;
|
124
|
+
|
125
|
+
/** If the watcher thread threw an uncaught exception then its information will
|
126
|
+
* be stored here so that the main thread can check whether a watcher encountered
|
127
|
+
* an error. These are empty strings if everything is OK.
|
128
|
+
*/
|
129
|
+
string threadExceptionMessage;
|
130
|
+
string threadExceptionBacktrace;
|
131
|
+
|
132
|
+
/** The agent process's feedback fd. */
|
133
|
+
FileDescriptor feedbackFd;
|
134
|
+
|
135
|
+
/**
|
136
|
+
* Lock for protecting the exchange of data between the main thread and
|
137
|
+
* the watcher thread.
|
138
|
+
*/
|
139
|
+
mutable boost::mutex lock;
|
140
|
+
|
141
|
+
/**
|
142
|
+
* Returns the filename of the agent process's executable. This method may be
|
143
|
+
* called in a forked child process and may therefore not allocate memory.
|
144
|
+
*/
|
145
|
+
virtual string getExeFilename() const = 0;
|
146
|
+
|
147
|
+
/**
|
148
|
+
* This method is to exec() the agent with the right arguments.
|
149
|
+
* It is called from within a forked child process, so don't do any dynamic
|
150
|
+
* memory allocations in here. It must also not throw any exceptions.
|
151
|
+
* It must also preserve the value of errno after exec() is called.
|
152
|
+
*/
|
153
|
+
virtual void execProgram() const {
|
154
|
+
execl(getExeFilename().c_str(),
|
155
|
+
getExeFilename().c_str(),
|
156
|
+
"3", // feedback fd
|
157
|
+
(char *) 0);
|
158
|
+
}
|
159
|
+
|
160
|
+
/**
|
161
|
+
* This method is to send startup arguments to the agent process through
|
162
|
+
* the given file descriptor, which is the agent process's feedback fd.
|
163
|
+
* May throw arbitrary exceptions.
|
164
|
+
*/
|
165
|
+
virtual void sendStartupArguments(pid_t pid, FileDescriptor &fd) = 0;
|
166
|
+
|
167
|
+
/**
|
168
|
+
* This method is to process the startup info that the agent process has
|
169
|
+
* sent back. May throw arbitrary exceptions.
|
170
|
+
*/
|
171
|
+
virtual bool processStartupInfo(pid_t pid, FileDescriptor &fd, const vector<string> &args) = 0;
|
172
|
+
|
173
|
+
/**
|
174
|
+
* Kill a process with SIGKILL, and attempt to kill its children too.
|
175
|
+
* Then wait until it has quit.
|
176
|
+
*/
|
177
|
+
static void killAndWait(pid_t pid) {
|
178
|
+
this_thread::disable_interruption di;
|
179
|
+
this_thread::disable_syscall_interruption dsi;
|
180
|
+
// If the process is a process group leader then killing the
|
181
|
+
// group will likely kill all its child processes too.
|
182
|
+
if (syscalls::killpg(pid, SIGKILL) == -1) {
|
183
|
+
syscalls::kill(pid, SIGKILL);
|
184
|
+
}
|
185
|
+
syscalls::waitpid(pid, NULL, 0);
|
186
|
+
}
|
187
|
+
|
188
|
+
/**
|
189
|
+
* Behaves like <tt>waitpid(pid, status, WNOHANG)</tt>, but waits at most
|
190
|
+
* <em>timeout</em> miliseconds for the process to exit.
|
191
|
+
*/
|
192
|
+
static int timedWaitPid(pid_t pid, int *status, unsigned long long timeout) {
|
193
|
+
Timer timer;
|
194
|
+
int ret;
|
195
|
+
|
196
|
+
do {
|
197
|
+
ret = syscalls::waitpid(pid, status, WNOHANG);
|
198
|
+
if (ret > 0 || ret == -1) {
|
199
|
+
return ret;
|
200
|
+
} else {
|
201
|
+
syscalls::usleep(10000);
|
202
|
+
}
|
203
|
+
} while (timer.elapsed() < timeout);
|
204
|
+
return 0; // timed out
|
205
|
+
}
|
206
|
+
|
207
|
+
static void waitpidUsingKillPolling(pid_t pid) {
|
208
|
+
bool done = false;
|
209
|
+
|
210
|
+
while (!done) {
|
211
|
+
int ret = syscalls::kill(pid, 0);
|
212
|
+
done = ret == -1;
|
213
|
+
if (!done) {
|
214
|
+
syscalls::usleep(20000);
|
215
|
+
}
|
216
|
+
}
|
217
|
+
}
|
218
|
+
|
219
|
+
public:
|
220
|
+
AgentWatcher() {
|
221
|
+
thr = NULL;
|
222
|
+
pid = 0;
|
223
|
+
}
|
224
|
+
|
225
|
+
virtual ~AgentWatcher() {
|
226
|
+
delete thr;
|
227
|
+
}
|
228
|
+
|
229
|
+
/**
|
230
|
+
* Store information about the started agent process in the given report object.
|
231
|
+
* May throw arbitrary exceptions.
|
232
|
+
*
|
233
|
+
* @pre start() has been called and succeeded.
|
234
|
+
*/
|
235
|
+
virtual void reportAgentsInformation(VariantMap &report) = 0;
|
236
|
+
|
237
|
+
/** Returns the name of the agent that this class is watching. */
|
238
|
+
virtual const char *name() const = 0;
|
239
|
+
|
240
|
+
/**
|
241
|
+
* Starts the agent process. May throw arbitrary exceptions.
|
242
|
+
*/
|
243
|
+
virtual pid_t start() {
|
244
|
+
this_thread::disable_interruption di;
|
245
|
+
this_thread::disable_syscall_interruption dsi;
|
246
|
+
string exeFilename = getExeFilename();
|
247
|
+
SocketPair fds;
|
248
|
+
int e, ret;
|
249
|
+
pid_t pid;
|
250
|
+
|
251
|
+
/* Create feedback fd for this agent process. We'll send some startup
|
252
|
+
* arguments to this agent process through this fd, and we'll receive
|
253
|
+
* startup information through it as well.
|
254
|
+
*/
|
255
|
+
fds = createUnixSocketPair();
|
256
|
+
|
257
|
+
pid = syscalls::fork();
|
258
|
+
if (pid == 0) {
|
259
|
+
// Child
|
260
|
+
|
261
|
+
/* Make sure file descriptor FEEDBACK_FD refers to the newly created
|
262
|
+
* feedback fd (fds[1]) and close all other file descriptors.
|
263
|
+
* In this child process we don't care about the original FEEDBACK_FD
|
264
|
+
* (which is the Watchdog's communication channel to the agents starter.)
|
265
|
+
*
|
266
|
+
* fds[1] is guaranteed to be != FEEDBACK_FD because the watchdog
|
267
|
+
* is started with FEEDBACK_FD already assigned.
|
268
|
+
*/
|
269
|
+
syscalls::close(fds[0]);
|
270
|
+
|
271
|
+
if (syscalls::dup2(fds[1], FEEDBACK_FD) == -1) {
|
272
|
+
/* Something went wrong, report error through feedback fd. */
|
273
|
+
e = errno;
|
274
|
+
try {
|
275
|
+
writeArrayMessage(fds[1],
|
276
|
+
"system error before exec",
|
277
|
+
"dup2() failed",
|
278
|
+
toString(e).c_str(),
|
279
|
+
NULL);
|
280
|
+
_exit(1);
|
281
|
+
} catch (...) {
|
282
|
+
fprintf(stderr, "Passenger Watchdog: dup2() failed: %s (%d)\n",
|
283
|
+
strerror(e), e);
|
284
|
+
fflush(stderr);
|
285
|
+
_exit(1);
|
286
|
+
}
|
287
|
+
}
|
288
|
+
|
289
|
+
closeAllFileDescriptors(FEEDBACK_FD);
|
290
|
+
|
291
|
+
/* Become the process group leader so that the watchdog can kill the
|
292
|
+
* agent as well as all its descendant processes. */
|
293
|
+
setpgid(getpid(), getpid());
|
294
|
+
|
295
|
+
setOomScore(oldOomScore);
|
296
|
+
|
297
|
+
try {
|
298
|
+
execProgram();
|
299
|
+
} catch (...) {
|
300
|
+
fprintf(stderr, "PassengerWatchdog: execProgram() threw an exception\n");
|
301
|
+
fflush(stderr);
|
302
|
+
_exit(1);
|
303
|
+
}
|
304
|
+
e = errno;
|
305
|
+
try {
|
306
|
+
writeArrayMessage(FEEDBACK_FD,
|
307
|
+
"exec error",
|
308
|
+
toString(e).c_str(),
|
309
|
+
NULL);
|
310
|
+
} catch (...) {
|
311
|
+
fprintf(stderr, "Passenger Watchdog: could not execute %s: %s (%d)\n",
|
312
|
+
exeFilename.c_str(), strerror(e), e);
|
313
|
+
fflush(stderr);
|
314
|
+
}
|
315
|
+
_exit(1);
|
316
|
+
} else if (pid == -1) {
|
317
|
+
// Error
|
318
|
+
e = errno;
|
319
|
+
throw SystemException("Cannot fork a new process", e);
|
320
|
+
} else {
|
321
|
+
// Parent
|
322
|
+
FileDescriptor feedbackFd = fds[0];
|
323
|
+
vector<string> args;
|
324
|
+
|
325
|
+
fds[1].close();
|
326
|
+
this_thread::restore_interruption ri(di);
|
327
|
+
this_thread::restore_syscall_interruption rsi(dsi);
|
328
|
+
ScopeGuard failGuard(boost::bind(killAndWait, pid));
|
329
|
+
|
330
|
+
/* Send startup arguments. Ignore EPIPE and ECONNRESET here
|
331
|
+
* because the child process might have sent an feedback message
|
332
|
+
* without reading startup arguments.
|
333
|
+
*/
|
334
|
+
try {
|
335
|
+
sendStartupArguments(pid, feedbackFd);
|
336
|
+
} catch (const SystemException &ex) {
|
337
|
+
if (ex.code() != EPIPE && ex.code() != ECONNRESET) {
|
338
|
+
throw SystemException(string("Unable to start the ") + name() +
|
339
|
+
": an error occurred while sending startup arguments",
|
340
|
+
ex.code());
|
341
|
+
}
|
342
|
+
}
|
343
|
+
|
344
|
+
// Now read its feedback.
|
345
|
+
try {
|
346
|
+
ret = readArrayMessage(feedbackFd, args);
|
347
|
+
} catch (const SystemException &e) {
|
348
|
+
if (e.code() == ECONNRESET) {
|
349
|
+
ret = false;
|
350
|
+
} else {
|
351
|
+
throw SystemException(string("Unable to start the ") + name() +
|
352
|
+
": unable to read its startup information",
|
353
|
+
e.code());
|
354
|
+
}
|
355
|
+
}
|
356
|
+
if (!ret) {
|
357
|
+
this_thread::disable_interruption di2;
|
358
|
+
this_thread::disable_syscall_interruption dsi2;
|
359
|
+
int status;
|
360
|
+
|
361
|
+
/* The feedback fd was prematurely closed for an unknown reason.
|
362
|
+
* Did the agent process crash?
|
363
|
+
*
|
364
|
+
* We use timedWaitPid() here because if the process crashed
|
365
|
+
* because of an uncaught exception, the file descriptor
|
366
|
+
* might be closed before the process has printed an error
|
367
|
+
* message, so we give it some time to print the error
|
368
|
+
* before we kill it.
|
369
|
+
*/
|
370
|
+
ret = timedWaitPid(pid, &status, 5000);
|
371
|
+
if (ret == 0) {
|
372
|
+
/* Doesn't look like it; it seems it's still running.
|
373
|
+
* We can't do anything without proper feedback so kill
|
374
|
+
* the agent process and throw an exception.
|
375
|
+
*/
|
376
|
+
failGuard.runNow();
|
377
|
+
throw RuntimeException(string("Unable to start the ") + name() +
|
378
|
+
": it froze and reported an unknown error during its startup");
|
379
|
+
} else if (ret != -1 && WIFSIGNALED(status)) {
|
380
|
+
/* Looks like a crash which caused a signal. */
|
381
|
+
throw RuntimeException(string("Unable to start the ") + name() +
|
382
|
+
": it seems to have been killed with signal " +
|
383
|
+
getSignalName(WTERMSIG(status)) + " during startup");
|
384
|
+
} else if (ret == -1) {
|
385
|
+
/* Looks like it exited after detecting an error. */
|
386
|
+
throw RuntimeException(string("Unable to start the ") + name() +
|
387
|
+
": it seems to have crashed during startup for an unknown reason");
|
388
|
+
} else {
|
389
|
+
/* Looks like it exited after detecting an error, but has an exit code. */
|
390
|
+
throw RuntimeException(string("Unable to start the ") + name() +
|
391
|
+
": it seems to have crashed during startup for an unknown reason, "
|
392
|
+
"with exit code " + toString(WEXITSTATUS(status)));
|
393
|
+
}
|
394
|
+
}
|
395
|
+
|
396
|
+
if (args[0] == "system error before exec") {
|
397
|
+
throw SystemException(string("Unable to start the ") + name() +
|
398
|
+
": " + args[1], atoi(args[2]));
|
399
|
+
} else if (args[0] == "exec error") {
|
400
|
+
e = atoi(args[1]);
|
401
|
+
if (e == ENOENT) {
|
402
|
+
throw RuntimeException(string("Unable to start the ") + name() +
|
403
|
+
" because its executable (" + getExeFilename() + ") "
|
404
|
+
"doesn't exist. This probably means that your "
|
405
|
+
"Phusion Passenger installation is broken or "
|
406
|
+
"incomplete. Please reinstall Phusion Passenger");
|
407
|
+
} else {
|
408
|
+
throw SystemException(string("Unable to start the ") + name() +
|
409
|
+
" because exec(\"" + getExeFilename() + "\") failed",
|
410
|
+
atoi(args[1]));
|
411
|
+
}
|
412
|
+
} else if (!processStartupInfo(pid, feedbackFd, args)) {
|
413
|
+
throw RuntimeException(string("The ") + name() +
|
414
|
+
" sent an unknown startup info message '" +
|
415
|
+
args[0] + "'");
|
416
|
+
}
|
417
|
+
|
418
|
+
lock_guard<boost::mutex> l(lock);
|
419
|
+
this->feedbackFd = feedbackFd;
|
420
|
+
this->pid = pid;
|
421
|
+
failGuard.clear();
|
422
|
+
return pid;
|
423
|
+
}
|
424
|
+
}
|
425
|
+
|
426
|
+
/**
|
427
|
+
* Start watching the agent process.
|
428
|
+
*
|
429
|
+
* @pre start() has been called and succeeded.
|
430
|
+
* @pre This watcher isn't already watching.
|
431
|
+
* @throws RuntimeException If a precondition failed.
|
432
|
+
* @throws thread_interrupted
|
433
|
+
* @throws thread_resource_error
|
434
|
+
*/
|
435
|
+
virtual void startWatching() {
|
436
|
+
lock_guard<boost::mutex> l(lock);
|
437
|
+
if (pid == 0) {
|
438
|
+
throw RuntimeException("start() hasn't been called yet");
|
439
|
+
}
|
440
|
+
if (thr != NULL) {
|
441
|
+
throw RuntimeException("Already started watching.");
|
442
|
+
}
|
443
|
+
|
444
|
+
thr = new oxt::thread(boost::bind(&AgentWatcher::threadMain, this, shared_from_this()),
|
445
|
+
name(), 256 * 1024);
|
446
|
+
}
|
447
|
+
|
448
|
+
static void stopWatching(vector< shared_ptr<AgentWatcher> > &watchers) {
|
449
|
+
vector< shared_ptr<AgentWatcher> >::const_iterator it;
|
450
|
+
oxt::thread *threads[watchers.size()];
|
451
|
+
unsigned int i = 0;
|
452
|
+
|
453
|
+
for (it = watchers.begin(); it != watchers.end(); it++, i++) {
|
454
|
+
threads[i] = (*it)->thr;
|
455
|
+
}
|
456
|
+
|
457
|
+
oxt::thread::interrupt_and_join_multiple(threads, watchers.size());
|
458
|
+
for (it = watchers.begin(); it != watchers.end(); it++, i++) {
|
459
|
+
delete (*it)->thr;
|
460
|
+
(*it)->thr = NULL;
|
461
|
+
}
|
462
|
+
}
|
463
|
+
|
464
|
+
/**
|
465
|
+
* Force the agent process to shut down. Returns true if it was shut down,
|
466
|
+
* or false if it wasn't started.
|
467
|
+
*/
|
468
|
+
virtual bool forceShutdown() {
|
469
|
+
lock_guard<boost::mutex> l(lock);
|
470
|
+
if (pid == 0) {
|
471
|
+
return false;
|
472
|
+
} else {
|
473
|
+
killAndWait(pid);
|
474
|
+
this->pid = 0;
|
475
|
+
return true;
|
476
|
+
}
|
477
|
+
}
|
478
|
+
|
479
|
+
/**
|
480
|
+
* If the watcher thread has encountered an error, then the error message
|
481
|
+
* will be stored here. If the error message is empty then it means
|
482
|
+
* everything is still OK.
|
483
|
+
*/
|
484
|
+
string getErrorMessage() const {
|
485
|
+
lock_guard<boost::mutex> l(lock);
|
486
|
+
return threadExceptionMessage;
|
487
|
+
}
|
488
|
+
|
489
|
+
/**
|
490
|
+
* The error backtrace, if applicable.
|
491
|
+
*/
|
492
|
+
string getErrorBacktrace() const {
|
493
|
+
lock_guard<boost::mutex> l(lock);
|
494
|
+
return threadExceptionBacktrace;
|
495
|
+
}
|
496
|
+
|
497
|
+
/**
|
498
|
+
* Returns the agent process feedback fd, or -1 if the agent process
|
499
|
+
* hasn't been started yet. Can be used to check whether this agent process
|
500
|
+
* has exited without using waitpid().
|
501
|
+
*/
|
502
|
+
const FileDescriptor getFeedbackFd() const {
|
503
|
+
lock_guard<boost::mutex> l(lock);
|
504
|
+
return feedbackFd;
|
505
|
+
}
|
506
|
+
};
|
507
|
+
|
508
|
+
typedef shared_ptr<AgentWatcher> AgentWatcherPtr;
|