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.

Files changed (79) hide show
  1. data.tar.gz.asc +7 -7
  2. data/NEWS +27 -0
  3. data/bin/passenger-config +6 -3
  4. data/bin/passenger-install-apache2-module +2 -2
  5. data/bin/passenger-install-nginx-module +16 -2
  6. data/build/agents.rb +4 -0
  7. data/build/apache2.rb +1 -1
  8. data/build/cplusplus_support.rb +1 -1
  9. data/build/cxx_tests.rb +3 -0
  10. data/build/packaging.rb +51 -8
  11. data/build/ruby_extension.rb +1 -1
  12. data/doc/Packaging.txt.md +20 -7
  13. data/doc/Users guide Apache.html +1 -1
  14. data/doc/Users guide Apache.txt +1 -1
  15. data/doc/Users guide Nginx.html +5 -4
  16. data/doc/Users guide Nginx.txt +1 -1
  17. data/doc/users_guide_snippets/installation.txt +5 -3
  18. data/ext/apache2/Configuration.cpp +12 -0
  19. data/ext/apache2/Configuration.hpp +7 -4
  20. data/ext/apache2/Hooks.cpp +29 -19
  21. data/ext/common/AgentsStarter.cpp +85 -57
  22. data/ext/common/AgentsStarter.h +570 -42
  23. data/ext/common/ApplicationPool2/DirectSpawner.h +5 -2
  24. data/ext/common/ApplicationPool2/Implementation.cpp +7 -1
  25. data/ext/common/ApplicationPool2/Pool.h +6 -3
  26. data/ext/common/ApplicationPool2/Process.h +12 -3
  27. data/ext/common/ApplicationPool2/SmartSpawner.h +2 -1
  28. data/ext/common/Constants.h +4 -1
  29. data/ext/common/EventedBufferedInput.h +139 -16
  30. data/ext/common/MultiLibeio.cpp +4 -2
  31. data/ext/common/SafeLibev.h +15 -62
  32. data/ext/common/ServerInstanceDir.h +10 -26
  33. data/ext/common/Utils.cpp +1 -3
  34. data/ext/common/Utils.h +1 -1
  35. data/ext/common/Utils/StrIntUtils.cpp +9 -0
  36. data/ext/common/Utils/StrIntUtils.h +5 -0
  37. data/ext/common/Utils/VariantMap.h +63 -14
  38. data/ext/common/agents/Base.cpp +50 -15
  39. data/ext/common/agents/HelperAgent/AgentOptions.h +20 -12
  40. data/ext/common/agents/HelperAgent/FileBackedPipe.h +1 -1
  41. data/ext/common/agents/HelperAgent/Main.cpp +5 -4
  42. data/ext/common/agents/HelperAgent/RequestHandler.h +1 -1
  43. data/ext/common/agents/LoggingAgent/Main.cpp +0 -1
  44. data/ext/common/agents/LoggingAgent/RemoteSender.h +2 -2
  45. data/ext/common/agents/SpawnPreparer.cpp +23 -5
  46. data/ext/common/agents/Watchdog/AgentWatcher.cpp +508 -0
  47. data/ext/common/agents/Watchdog/HelperAgentWatcher.cpp +93 -0
  48. data/ext/common/agents/Watchdog/LoggingAgentWatcher.cpp +68 -0
  49. data/ext/common/agents/Watchdog/Main.cpp +180 -802
  50. data/ext/common/agents/Watchdog/ServerInstanceDirToucher.cpp +111 -0
  51. data/ext/nginx/Configuration.c +107 -92
  52. data/ext/nginx/Configuration.h +1 -0
  53. data/ext/nginx/ContentHandler.c +6 -6
  54. data/ext/nginx/ContentHandler.h +1 -1
  55. data/ext/nginx/config +8 -2
  56. data/ext/nginx/ngx_http_passenger_module.c +54 -60
  57. data/ext/nginx/ngx_http_passenger_module.h +6 -6
  58. data/lib/phusion_passenger.rb +17 -10
  59. data/lib/phusion_passenger/admin_tools/server_instance.rb +2 -2
  60. data/lib/phusion_passenger/common_library.rb +0 -1
  61. data/lib/phusion_passenger/platform_info.rb +10 -1
  62. data/lib/phusion_passenger/platform_info/depcheck.rb +4 -4
  63. data/lib/phusion_passenger/platform_info/depcheck_specs/compiler_toolchain.rb +2 -2
  64. data/lib/phusion_passenger/platform_info/ruby.rb +7 -0
  65. data/lib/phusion_passenger/request_handler.rb +119 -42
  66. data/lib/phusion_passenger/request_handler/thread_handler.rb +25 -22
  67. data/lib/phusion_passenger/standalone/command.rb +2 -0
  68. data/lib/phusion_passenger/standalone/runtime_installer.rb +4 -3
  69. data/lib/phusion_passenger/standalone/start_command.rb +49 -37
  70. data/resources/templates/nginx/pcre_checksum_could_not_be_verified.txt.erb +11 -0
  71. data/test/cxx/CxxTestMain.cpp +2 -0
  72. data/test/cxx/EventedBufferedInputTest.cpp +758 -0
  73. data/test/cxx/ServerInstanceDirTest.cpp +16 -31
  74. data/test/cxx/TestSupport.cpp +2 -1
  75. data/test/cxx/VariantMapTest.cpp +23 -11
  76. metadata +8 -4
  77. metadata.gz.asc +7 -7
  78. data/ext/common/AgentsStarter.hpp +0 -655
  79. data/lib/phusion_passenger/utils/robust_interruption.rb +0 -173
@@ -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 ret = fcntl(FEEDBACK_FD, F_GETFL);
1464
- if (ret == -1) {
1465
- if (errno == EBADF) {
1466
- fprintf(stderr,
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 messageSocketPassword;
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 = options.get("passenger_root");
63
- tempDir = options.get("temp_dir");
64
- userSwitching = options.getBool("user_switching");
65
- defaultRubyCommand = options.get("default_ruby");
66
- defaultUser = options.get("default_user");
67
- defaultGroup = options.get("default_group");
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
- requestSocketPassword = Base64::decode(options.get("request_socket_password"));
76
- messageSocketPassword = Base64::decode(options.get("message_socket_password"));
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.get("prestart_urls", false, "");
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()->runAsync(boost::bind(
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(options.webServerPid, options.tempDir, false),
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.messageSocketPassword, false, Account::EXIT);
387
- messageServer = ptr(new MessageServer(generation->getPath() + "/socket", accountsDatabase));
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 generation->getPath() + "/request.socket";
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->runLaterTS(boost::bind(&RequestHandler::sessionCheckedOut_real, this,
1863
+ libev->runLater(boost::bind(&RequestHandler::sessionCheckedOut_real, this,
1864
1864
  client, session, e));
1865
1865
  } else {
1866
1866
  sessionCheckedOut_real(client, session, e);
@@ -41,7 +41,6 @@
41
41
 
42
42
  #include <AccountsDatabase.h>
43
43
  #include <Account.h>
44
- #include <ServerInstanceDir.h>
45
44
  #include <Exceptions.h>
46
45
  #include <Utils.h>
47
46
  #include <Utils/IOUtils.h>
@@ -543,8 +543,8 @@ public:
543
543
  }
544
544
  stream << "\n";
545
545
  stream << " Items in queue: " << queue.size() << "\n";
546
- stream << " Packet sent out so far: " << packetsSent << "\n";
547
- stream << " Packet dropped out so far: " << packetsDropped << "\n";
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 < 4) {
166
+ if (argc < 5) {
151
167
  fprintf(stderr, "Too few arguments.\n");
152
168
  exit(1);
153
169
  }
154
170
 
155
- const char *envvars = argv[1];
156
- const char *executable = argv[2];
157
- char **execArgs = &argv[3];
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;