passenger 3.9.2.beta → 4.0.0.rc4

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 (159) hide show
  1. data/.travis.yml +3 -0
  2. data/NEWS +77 -7
  3. data/README.md +3 -11
  4. data/bin/passenger-install-apache2-module +24 -20
  5. data/bin/passenger-install-nginx-module +25 -23
  6. data/build/agents.rb +11 -0
  7. data/build/apache2.rb +9 -5
  8. data/build/basics.rb +37 -30
  9. data/build/common_library.rb +4 -1
  10. data/build/cplusplus_support.rb +5 -5
  11. data/build/cxx_tests.rb +28 -8
  12. data/build/integration_tests.rb +6 -3
  13. data/build/nginx.rb +3 -3
  14. data/build/packaging.rb +95 -57
  15. data/build/ruby_extension.rb +34 -21
  16. data/build/ruby_tests.rb +4 -2
  17. data/build/test_basics.rb +1 -1
  18. data/dev/run_travis.sh +36 -1
  19. data/doc/Users guide Apache.html +425 -308
  20. data/doc/Users guide Apache.idmap.txt +78 -70
  21. data/doc/Users guide Apache.index.sqlite3 +0 -0
  22. data/doc/Users guide Apache.txt +33 -92
  23. data/doc/Users guide Nginx.html +519 -220
  24. data/doc/Users guide Nginx.idmap.txt +78 -60
  25. data/doc/Users guide Nginx.txt +115 -26
  26. data/doc/Users guide Standalone.html +8 -2
  27. data/doc/users_guide_snippets/analysis_and_system_maintenance.txt +1 -7
  28. data/doc/users_guide_snippets/installation.txt +167 -22
  29. data/doc/users_guide_snippets/rackup_specifications.txt +4 -0
  30. data/doc/users_guide_snippets/since_version.txt +1 -0
  31. data/doc/users_guide_snippets/support_information.txt +3 -7
  32. data/doc/users_guide_snippets/tips.txt +0 -24
  33. data/ext/apache2/Configuration.cpp +11 -33
  34. data/ext/apache2/Configuration.hpp +3 -18
  35. data/ext/apache2/DirectoryMapper.h +20 -70
  36. data/ext/apache2/Hooks.cpp +2 -2
  37. data/ext/common/AgentsStarter.cpp +0 -2
  38. data/ext/common/AgentsStarter.h +0 -1
  39. data/ext/common/AgentsStarter.hpp +1 -3
  40. data/ext/common/ApplicationPool2/AppTypes.cpp +74 -0
  41. data/ext/common/ApplicationPool2/AppTypes.h +202 -0
  42. data/ext/common/ApplicationPool2/Common.h +12 -10
  43. data/ext/common/ApplicationPool2/DirectSpawner.h +256 -0
  44. data/ext/common/ApplicationPool2/DummySpawner.h +90 -0
  45. data/ext/common/ApplicationPool2/Group.h +311 -94
  46. data/ext/common/ApplicationPool2/Implementation.cpp +405 -145
  47. data/ext/common/ApplicationPool2/Options.h +24 -26
  48. data/ext/common/ApplicationPool2/PipeWatcher.h +20 -13
  49. data/ext/common/ApplicationPool2/Pool.h +326 -183
  50. data/ext/common/ApplicationPool2/Process.h +205 -55
  51. data/ext/common/ApplicationPool2/README.md +1 -1
  52. data/ext/common/ApplicationPool2/Session.h +21 -10
  53. data/ext/common/ApplicationPool2/SmartSpawner.h +801 -0
  54. data/ext/common/ApplicationPool2/Spawner.h +141 -1149
  55. data/ext/common/ApplicationPool2/SpawnerFactory.h +132 -0
  56. data/ext/common/ApplicationPool2/SuperGroup.h +146 -223
  57. data/ext/common/Constants.h +4 -2
  58. data/ext/common/Exceptions.h +23 -1
  59. data/ext/common/Logging.cpp +17 -6
  60. data/ext/common/Logging.h +37 -7
  61. data/ext/common/ResourceLocator.h +1 -1
  62. data/ext/common/Utils.cpp +49 -1
  63. data/ext/common/Utils.h +13 -4
  64. data/ext/common/{AnsiColorConstants.h → Utils/AnsiColorConstants.h} +0 -0
  65. data/ext/common/{BCrypt.cpp → Utils/BCrypt.cpp} +0 -0
  66. data/ext/common/{BCrypt.h → Utils/BCrypt.h} +0 -0
  67. data/ext/common/{Blowfish.c → Utils/Blowfish.c} +0 -0
  68. data/ext/common/{Blowfish.h → Utils/Blowfish.h} +0 -0
  69. data/ext/common/Utils/CachedFileStat.hpp +27 -25
  70. data/ext/common/Utils/Curl.h +184 -0
  71. data/ext/common/{HttpConstants.h → Utils/HttpConstants.h} +3 -0
  72. data/ext/common/Utils/IOUtils.cpp +6 -2
  73. data/ext/common/{IniFile.h → Utils/IniFile.h} +0 -0
  74. data/ext/common/Utils/LargeFiles.cpp +30 -0
  75. data/ext/common/Utils/LargeFiles.h +40 -0
  76. data/ext/common/Utils/StrIntUtils.cpp +72 -8
  77. data/ext/common/Utils/StrIntUtils.h +24 -2
  78. data/ext/common/Utils/StringMap.h +12 -2
  79. data/ext/common/Utils/VariantMap.h +51 -2
  80. data/ext/common/Utils/jsoncpp.cpp +1 -1
  81. data/ext/common/agents/Base.cpp +147 -11
  82. data/ext/common/agents/HelperAgent/AgentOptions.h +14 -6
  83. data/ext/common/agents/HelperAgent/Main.cpp +79 -19
  84. data/ext/common/agents/HelperAgent/RequestHandler.h +36 -16
  85. data/ext/common/agents/LoggingAgent/LoggingServer.h +3 -5
  86. data/ext/common/agents/LoggingAgent/Main.cpp +2 -4
  87. data/ext/common/agents/LoggingAgent/RemoteSender.h +18 -24
  88. data/ext/common/agents/SpawnPreparer.cpp +7 -0
  89. data/ext/common/agents/Watchdog/Main.cpp +96 -38
  90. data/ext/nginx/Configuration.c +26 -22
  91. data/ext/nginx/Configuration.h +4 -2
  92. data/ext/nginx/ContentHandler.c +23 -52
  93. data/ext/nginx/ContentHandler.h +5 -11
  94. data/ext/nginx/config +10 -3
  95. data/ext/nginx/ngx_http_passenger_module.c +21 -6
  96. data/ext/nginx/ngx_http_passenger_module.h +4 -1
  97. data/ext/oxt/dynamic_thread_group.hpp +9 -1
  98. data/ext/oxt/system_calls.cpp +2 -2
  99. data/ext/ruby/extconf.rb +2 -1
  100. data/helper-scripts/backtrace-sanitizer.rb +2 -0
  101. data/helper-scripts/wsgi-loader.py +54 -21
  102. data/lib/phusion_passenger.rb +5 -3
  103. data/lib/phusion_passenger/abstract_installer.rb +18 -41
  104. data/lib/phusion_passenger/admin_tools/memory_stats.rb +2 -2
  105. data/lib/phusion_passenger/admin_tools/server_instance.rb +2 -2
  106. data/lib/phusion_passenger/common_library.rb +23 -3
  107. data/lib/phusion_passenger/debug_logging.rb +10 -3
  108. data/lib/phusion_passenger/packaging.rb +1 -0
  109. data/lib/phusion_passenger/platform_info.rb +113 -115
  110. data/lib/phusion_passenger/platform_info/compiler.rb +224 -134
  111. data/lib/phusion_passenger/platform_info/cxx_portability.rb +143 -0
  112. data/lib/phusion_passenger/platform_info/depcheck.rb +371 -0
  113. data/lib/phusion_passenger/platform_info/depcheck_specs/apache2.rb +124 -0
  114. data/lib/phusion_passenger/platform_info/depcheck_specs/compiler_toolchain.rb +97 -0
  115. data/lib/phusion_passenger/platform_info/depcheck_specs/gems.rb +39 -0
  116. data/lib/phusion_passenger/platform_info/depcheck_specs/libs.rb +118 -0
  117. data/lib/phusion_passenger/platform_info/depcheck_specs/ruby.rb +137 -0
  118. data/lib/phusion_passenger/platform_info/depcheck_specs/utilities.rb +15 -0
  119. data/lib/phusion_passenger/platform_info/operating_system.rb +6 -5
  120. data/lib/phusion_passenger/platform_info/ruby.rb +45 -34
  121. data/lib/phusion_passenger/request_handler.rb +35 -22
  122. data/lib/phusion_passenger/request_handler/thread_handler.rb +5 -6
  123. data/lib/phusion_passenger/ruby_core_enhancements.rb +7 -1
  124. data/lib/phusion_passenger/standalone/runtime_installer.rb +43 -34
  125. data/lib/phusion_passenger/utils/robust_interruption.rb +34 -18
  126. data/passenger.gemspec +25 -0
  127. data/resources/templates/standalone/config.erb +3 -1
  128. data/test/config.json.travis +2 -2
  129. data/test/cxx/ApplicationPool2/DirectSpawnerTest.cpp +37 -5
  130. data/test/cxx/ApplicationPool2/PoolTest.cpp +143 -50
  131. data/test/cxx/ApplicationPool2/ProcessTest.cpp +8 -0
  132. data/test/cxx/ApplicationPool2/SmartSpawnerTest.cpp +28 -17
  133. data/test/cxx/ApplicationPool2/SpawnerTestCases.cpp +31 -26
  134. data/test/cxx/RequestHandlerTest.cpp +17 -1
  135. data/test/cxx/UtilsTest.cpp +84 -10
  136. data/test/integration_tests/apache2_tests.rb +49 -163
  137. data/test/integration_tests/hello_world_wsgi_spec.rb +2 -2
  138. data/test/integration_tests/mycook_spec.rb +1 -1
  139. data/test/integration_tests/nginx_tests.rb +37 -19
  140. data/test/ruby/request_handler_spec.rb +1 -0
  141. data/test/ruby/spec_helper.rb +52 -1
  142. data/test/stub/nginx/nginx.conf.erb +2 -0
  143. data/test/stub/rack/start.rb +5 -0
  144. data/test/stub/rails3.0/Gemfile.lock +30 -30
  145. data/test/stub/rails3.1/Gemfile +1 -1
  146. data/test/stub/rails3.1/Gemfile.lock +3 -3
  147. data/test/stub/rails3.2/Gemfile +1 -1
  148. data/test/stub/rails3.2/Gemfile.lock +4 -4
  149. data/test/stub/rails_apps/2.3/mycook/app/controllers/welcome_controller.rb +1 -1
  150. data/test/stub/rails_apps/2.3/mycook/app/helpers/recipes_helper.rb +2 -0
  151. data/test/stub/rails_apps/2.3/mycook/app/helpers/test_helper.rb +2 -0
  152. data/test/stub/rails_apps/2.3/mycook/app/helpers/uploads_helper.rb +2 -0
  153. data/test/stub/rails_apps/2.3/mycook/app/helpers/welcome_helper.rb +2 -0
  154. data/test/support/nginx_controller.rb +2 -1
  155. metadata +160 -156
  156. data/build/gempackagetask.rb +0 -99
  157. data/build/packagetask.rb +0 -186
  158. data/ext/common/StringListCreator.h +0 -83
  159. data/lib/phusion_passenger/dependencies.rb +0 -657
@@ -0,0 +1,801 @@
1
+ /*
2
+ * Phusion Passenger - https://www.phusionpassenger.com/
3
+ * Copyright (c) 2011-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
+ #ifndef _PASSENGER_APPLICATION_POOL2_SMART_SPAWNER_H_
26
+ #define _PASSENGER_APPLICATION_POOL2_SMART_SPAWNER_H_
27
+
28
+ #include <ApplicationPool2/Spawner.h>
29
+
30
+ namespace Passenger {
31
+ namespace ApplicationPool2 {
32
+
33
+ using namespace std;
34
+ using namespace boost;
35
+ using namespace oxt;
36
+
37
+
38
+ class SmartSpawner: public Spawner, public enable_shared_from_this<SmartSpawner> {
39
+ private:
40
+ /**
41
+ * Structure containing arguments and working state for negotiating
42
+ * the preloader startup protocol.
43
+ */
44
+ struct StartupDetails {
45
+ /****** Arguments ******/
46
+ pid_t pid;
47
+ FileDescriptor adminSocket;
48
+ BufferedIO io;
49
+ BackgroundIOCapturerPtr stderrCapturer;
50
+ DebugDirPtr debugDir;
51
+ const Options *options;
52
+ bool forwardStderr;
53
+ int forwardStderrTo;
54
+
55
+ /****** Working state ******/
56
+ unsigned long long timeout;
57
+
58
+ StartupDetails() {
59
+ options = NULL;
60
+ forwardStderr = false;
61
+ forwardStderrTo = STDERR_FILENO;
62
+ timeout = 0;
63
+ }
64
+ };
65
+
66
+ struct SpawnResult {
67
+ pid_t pid;
68
+ FileDescriptor adminSocket;
69
+ BufferedIO io;
70
+ };
71
+
72
+ /** The event loop that created Process objects should use, and that I/O forwarding
73
+ * functions should use. For example data on the error pipe is forwarded using this event loop.
74
+ */
75
+ SafeLibevPtr libev;
76
+ const vector<string> preloaderCommand;
77
+ map<string, string> preloaderAnnotations;
78
+ Options options;
79
+
80
+ // Protects m_lastUsed and pid.
81
+ mutable boost::mutex simpleFieldSyncher;
82
+ // Protects everything else.
83
+ mutable boost::mutex syncher;
84
+
85
+ // Preloader information.
86
+ pid_t pid;
87
+ FileDescriptor adminSocket;
88
+ string socketAddress;
89
+ unsigned long long m_lastUsed;
90
+ // Upon starting the preloader, its preparation info is stored here
91
+ // for future reference.
92
+ SpawnPreparationInfo preparation;
93
+
94
+ string getPreloaderCommandString() const {
95
+ string result;
96
+ unsigned int i;
97
+
98
+ for (i = 0; i < preloaderCommand.size(); i++) {
99
+ if (i != 0) {
100
+ result.append(1, '\0');
101
+ }
102
+ result.append(preloaderCommand[i]);
103
+ }
104
+ return result;
105
+ }
106
+
107
+ vector<string> createRealPreloaderCommand(const Options &options,
108
+ shared_array<const char *> &args)
109
+ {
110
+ string agentsDir = resourceLocator.getAgentsDir();
111
+ vector<string> command;
112
+
113
+ if (options.loadShellEnvvars) {
114
+ command.push_back("bash");
115
+ command.push_back("bash");
116
+ command.push_back("-lc");
117
+ command.push_back("exec \"$@\"");
118
+ command.push_back("SpawnPreparerShell");
119
+ } else {
120
+ command.push_back(agentsDir + "/SpawnPreparer");
121
+ }
122
+ command.push_back(agentsDir + "/SpawnPreparer");
123
+ command.push_back(serializeEnvvarsFromPoolOptions(options));
124
+ command.push_back(preloaderCommand[0]);
125
+ command.push_back("Passenger AppPreloader: " + options.appRoot);
126
+ for (unsigned int i = 1; i < preloaderCommand.size(); i++) {
127
+ command.push_back(preloaderCommand[i]);
128
+ }
129
+
130
+ createCommandArgs(command, args);
131
+ return command;
132
+ }
133
+
134
+ void throwPreloaderSpawnException(const string &msg,
135
+ SpawnException::ErrorKind errorKind,
136
+ StartupDetails &details)
137
+ {
138
+ throwPreloaderSpawnException(msg, errorKind, details.stderrCapturer,
139
+ details.debugDir);
140
+ }
141
+
142
+ void throwPreloaderSpawnException(const string &msg,
143
+ SpawnException::ErrorKind errorKind,
144
+ BackgroundIOCapturerPtr &stderrCapturer,
145
+ const DebugDirPtr &debugDir)
146
+ {
147
+ TRACE_POINT();
148
+ // Stop the stderr capturing thread and get the captured stderr
149
+ // output so far.
150
+ string stderrOutput;
151
+ if (stderrCapturer != NULL) {
152
+ stderrOutput = stderrCapturer->stop();
153
+ }
154
+
155
+ // If the exception wasn't due to a timeout, try to capture the
156
+ // remaining stderr output for at most 2 seconds.
157
+ if (errorKind != SpawnException::PRELOADER_STARTUP_TIMEOUT
158
+ && errorKind != SpawnException::APP_STARTUP_TIMEOUT
159
+ && stderrCapturer != NULL) {
160
+ bool done = false;
161
+ unsigned long long timeout = 2000;
162
+ while (!done) {
163
+ char buf[1024 * 32];
164
+ unsigned int ret;
165
+
166
+ try {
167
+ ret = readExact(stderrCapturer->getFd(), buf,
168
+ sizeof(buf), &timeout);
169
+ if (ret == 0) {
170
+ done = true;
171
+ } else {
172
+ stderrOutput.append(buf, ret);
173
+ }
174
+ } catch (const SystemException &e) {
175
+ P_WARN("Stderr I/O capture error: " << e.what());
176
+ done = true;
177
+ } catch (const TimeoutException &) {
178
+ done = true;
179
+ }
180
+ }
181
+ }
182
+ stderrCapturer.reset();
183
+
184
+ // Now throw SpawnException with the captured stderr output
185
+ // as error response.
186
+ SpawnException e(msg, stderrOutput, false, errorKind);
187
+ e.setPreloaderCommand(getPreloaderCommandString());
188
+ annotatePreloaderException(e, debugDir);
189
+ throw e;
190
+ }
191
+
192
+ void annotatePreloaderException(SpawnException &e, const DebugDirPtr &debugDir) {
193
+ if (debugDir != NULL) {
194
+ e.addAnnotations(debugDir->readAll());
195
+ }
196
+ }
197
+
198
+ bool preloaderStarted() const {
199
+ return pid != -1;
200
+ }
201
+
202
+ void startPreloader() {
203
+ TRACE_POINT();
204
+ this_thread::disable_interruption di;
205
+ this_thread::disable_syscall_interruption dsi;
206
+ assert(!preloaderStarted());
207
+ P_DEBUG("Spawning new preloader: appRoot=" << options.appRoot);
208
+ checkChrootDirectories(options);
209
+
210
+ shared_array<const char *> args;
211
+ vector<string> command = createRealPreloaderCommand(options, args);
212
+ preparation = prepareSpawn(options);
213
+ SocketPair adminSocket = createUnixSocketPair();
214
+ Pipe errorPipe = createPipe();
215
+ DebugDirPtr debugDir = make_shared<DebugDir>(preparation.uid, preparation.gid);
216
+ pid_t pid;
217
+
218
+ pid = syscalls::fork();
219
+ if (pid == 0) {
220
+ setenv("PASSENGER_DEBUG_DIR", debugDir->getPath().c_str(), 1);
221
+ purgeStdio(stdout);
222
+ purgeStdio(stderr);
223
+ resetSignalHandlersAndMask();
224
+ disableMallocDebugging();
225
+ int adminSocketCopy = dup2(adminSocket.first, 3);
226
+ int errorPipeCopy = dup2(errorPipe.second, 4);
227
+ dup2(adminSocketCopy, 0);
228
+ dup2(adminSocketCopy, 1);
229
+ dup2(errorPipeCopy, 2);
230
+ closeAllFileDescriptors(2);
231
+ setChroot(preparation);
232
+ switchUser(preparation);
233
+ setWorkingDirectory(preparation);
234
+ execvp(command[0].c_str(), (char * const *) args.get());
235
+
236
+ int e = errno;
237
+ printf("!> Error\n");
238
+ printf("!> \n");
239
+ printf("Cannot execute \"%s\": %s (errno=%d)\n", command[0].c_str(),
240
+ strerror(e), e);
241
+ fprintf(stderr, "Cannot execute \"%s\": %s (errno=%d)\n",
242
+ command[0].c_str(), strerror(e), e);
243
+ fflush(stdout);
244
+ fflush(stderr);
245
+ _exit(1);
246
+
247
+ } else if (pid == -1) {
248
+ int e = errno;
249
+ throw SystemException("Cannot fork a new process", e);
250
+
251
+ } else {
252
+ ScopeGuard guard(boost::bind(nonInterruptableKillAndWaitpid, pid));
253
+ P_DEBUG("Preloader process forked for appRoot=" << options.appRoot << ": PID " << pid);
254
+ adminSocket.first.close();
255
+ errorPipe.second.close();
256
+
257
+ StartupDetails details;
258
+ details.pid = pid;
259
+ details.adminSocket = adminSocket.second;
260
+ details.io = BufferedIO(adminSocket.second);
261
+ details.stderrCapturer =
262
+ make_shared<BackgroundIOCapturer>(
263
+ errorPipe.first,
264
+ string("[App ") + toString(pid) + " stderr] ",
265
+ config->forwardStderr);
266
+ details.stderrCapturer->start();
267
+ details.debugDir = debugDir;
268
+ details.options = &options;
269
+ details.timeout = options.startTimeout * 1000;
270
+ details.forwardStderr = config->forwardStderr;
271
+
272
+ {
273
+ this_thread::restore_interruption ri(di);
274
+ this_thread::restore_syscall_interruption rsi(dsi);
275
+ socketAddress = negotiatePreloaderStartup(details);
276
+ }
277
+ this->adminSocket = adminSocket.second;
278
+ {
279
+ lock_guard<boost::mutex> l(simpleFieldSyncher);
280
+ this->pid = pid;
281
+ }
282
+
283
+ PipeWatcherPtr watcher;
284
+
285
+ watcher = make_shared<PipeWatcher>(adminSocket.second,
286
+ "stdout", pid, config->forwardStdout);
287
+ watcher->initialize();
288
+ watcher->start();
289
+
290
+ watcher = make_shared<PipeWatcher>(errorPipe.first,
291
+ "stderr", pid, config->forwardStderr);
292
+ watcher->initialize();
293
+ watcher->start();
294
+
295
+ preloaderAnnotations = debugDir->readAll();
296
+ P_INFO("Preloader for " << options.appRoot <<
297
+ " started on PID " << pid <<
298
+ ", listening on " << socketAddress);
299
+ guard.clear();
300
+ }
301
+ }
302
+
303
+ void stopPreloader() {
304
+ TRACE_POINT();
305
+ this_thread::disable_interruption di;
306
+ this_thread::disable_syscall_interruption dsi;
307
+
308
+ if (!preloaderStarted()) {
309
+ return;
310
+ }
311
+ syscalls::shutdown(adminSocket, SHUT_WR);
312
+ if (timedWaitpid(pid, NULL, 5000) == 0) {
313
+ P_TRACE(2, "Spawn server did not exit in time, killing it...");
314
+ syscalls::kill(pid, SIGKILL);
315
+ syscalls::waitpid(pid, NULL, 0);
316
+ }
317
+ // Delete socket after the process has exited so that it
318
+ // doesn't crash upon deleting a nonexistant file.
319
+ if (getSocketAddressType(socketAddress) == SAT_UNIX) {
320
+ string filename = parseUnixSocketAddress(socketAddress);
321
+ syscalls::unlink(filename.c_str());
322
+ }
323
+ {
324
+ lock_guard<boost::mutex> l(simpleFieldSyncher);
325
+ pid = -1;
326
+ }
327
+ socketAddress.clear();
328
+ preparation = SpawnPreparationInfo();
329
+ }
330
+
331
+ void sendStartupRequest(StartupDetails &details) {
332
+ TRACE_POINT();
333
+ try {
334
+ string data = "You have control 1.0\n"
335
+ "passenger_root: " + resourceLocator.getRoot() + "\n"
336
+ "ruby_libdir: " + resourceLocator.getRubyLibDir() + "\n"
337
+ "passenger_version: " PASSENGER_VERSION "\n"
338
+ "generation_dir: " + generation->getPath() + "\n";
339
+
340
+ vector<string> args;
341
+ vector<string>::const_iterator it, end;
342
+ details.options->toVector(args, resourceLocator);
343
+ for (it = args.begin(); it != args.end(); it++) {
344
+ const string &key = *it;
345
+ it++;
346
+ const string &value = *it;
347
+ data.append(key + ": " + value + "\n");
348
+ }
349
+
350
+ vector<StaticString> lines;
351
+ split(data, '\n', lines);
352
+ foreach (const StaticString line, lines) {
353
+ P_DEBUG("[App " << details.pid << " stdin >>] " << line);
354
+ }
355
+ writeExact(details.adminSocket, data, &details.timeout);
356
+ writeExact(details.adminSocket, "\n", &details.timeout);
357
+ } catch (const SystemException &e) {
358
+ if (e.code() == EPIPE) {
359
+ /* Ignore this. Process might have written an
360
+ * error response before reading the arguments,
361
+ * in which case we'll want to show that instead.
362
+ */
363
+ } else {
364
+ throwPreloaderSpawnException("An error occurred while starting up "
365
+ "the preloader. There was an I/O error while "
366
+ "sending the startup request message to it: " +
367
+ e.sys(),
368
+ SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
369
+ details);
370
+ }
371
+ } catch (const TimeoutException &) {
372
+ throwPreloaderSpawnException("An error occurred while starting up the "
373
+ "preloader: it did not read the startup request message in time.",
374
+ SpawnException::PRELOADER_STARTUP_TIMEOUT,
375
+ details);
376
+ }
377
+ }
378
+
379
+ string handleStartupResponse(StartupDetails &details) {
380
+ TRACE_POINT();
381
+ string socketAddress;
382
+
383
+ while (true) {
384
+ string line;
385
+
386
+ try {
387
+ line = readMessageLine(details);
388
+ } catch (const SystemException &e) {
389
+ throwPreloaderSpawnException("An error occurred while starting up "
390
+ "the preloader. There was an I/O error while reading its "
391
+ "startup response: " + e.sys(),
392
+ SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
393
+ details);
394
+ } catch (const TimeoutException &) {
395
+ throwPreloaderSpawnException("An error occurred while starting up "
396
+ "the preloader: it did not write a startup response in time.",
397
+ SpawnException::PRELOADER_STARTUP_TIMEOUT,
398
+ details);
399
+ }
400
+
401
+ if (line.empty()) {
402
+ throwPreloaderSpawnException("An error occurred while starting up "
403
+ "the preloader. It unexpected closed the connection while "
404
+ "sending its startup response.",
405
+ SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
406
+ details);
407
+ } else if (line[line.size() - 1] != '\n') {
408
+ throwPreloaderSpawnException("An error occurred while starting up "
409
+ "the preloader. It sent a line without a newline character "
410
+ "in its startup response.",
411
+ SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
412
+ details);
413
+ } else if (line == "\n") {
414
+ break;
415
+ }
416
+
417
+ string::size_type pos = line.find(": ");
418
+ if (pos == string::npos) {
419
+ throwPreloaderSpawnException("An error occurred while starting up "
420
+ "the preloader. It sent a startup response line without "
421
+ "separator.",
422
+ SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
423
+ details);
424
+ }
425
+
426
+ string key = line.substr(0, pos);
427
+ string value = line.substr(pos + 2, line.size() - pos - 3);
428
+ if (key == "socket") {
429
+ // TODO: validate socket address here
430
+ socketAddress = fixupSocketAddress(options, value);
431
+ } else {
432
+ throwPreloaderSpawnException("An error occurred while starting up "
433
+ "the preloader. It sent an unknown startup response line "
434
+ "called '" + key + "'.",
435
+ SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
436
+ details);
437
+ }
438
+ }
439
+
440
+ if (socketAddress.empty()) {
441
+ throwPreloaderSpawnException("An error occurred while starting up "
442
+ "the preloader. It did not report a socket address in its "
443
+ "startup response.",
444
+ SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
445
+ details);
446
+ }
447
+
448
+ return socketAddress;
449
+ }
450
+
451
+ void handleErrorResponse(StartupDetails &details) {
452
+ TRACE_POINT();
453
+ map<string, string> attributes;
454
+
455
+ while (true) {
456
+ string line;
457
+
458
+ try {
459
+ line = readMessageLine(details);
460
+ } catch (const SystemException &e) {
461
+ throwPreloaderSpawnException("An error occurred while starting up "
462
+ "the preloader. There was an I/O error while reading its "
463
+ "startup response: " + e.sys(),
464
+ SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
465
+ details);
466
+ } catch (const TimeoutException &) {
467
+ throwPreloaderSpawnException("An error occurred while starting up "
468
+ "the preloader: it did not write a startup response in time.",
469
+ SpawnException::PRELOADER_STARTUP_TIMEOUT,
470
+ details);
471
+ }
472
+
473
+ if (line.empty()) {
474
+ throwPreloaderSpawnException("An error occurred while starting up "
475
+ "the preloader. It unexpected closed the connection while "
476
+ "sending its startup response.",
477
+ SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
478
+ details);
479
+ } else if (line[line.size() - 1] != '\n') {
480
+ throwPreloaderSpawnException("An error occurred while starting up "
481
+ "the preloader. It sent a line without a newline character "
482
+ "in its startup response.",
483
+ SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
484
+ details);
485
+ } else if (line == "\n") {
486
+ break;
487
+ }
488
+
489
+ string::size_type pos = line.find(": ");
490
+ if (pos == string::npos) {
491
+ throwPreloaderSpawnException("An error occurred while starting up "
492
+ "the preloader. It sent a startup response line without "
493
+ "separator.",
494
+ SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
495
+ details);
496
+ }
497
+
498
+ string key = line.substr(0, pos);
499
+ string value = line.substr(pos + 2, line.size() - pos - 3);
500
+ attributes[key] = value;
501
+ }
502
+
503
+ try {
504
+ string message = details.io.readAll(&details.timeout);
505
+ SpawnException e("An error occured while starting up the preloader.",
506
+ message,
507
+ attributes["html"] == "true",
508
+ SpawnException::PRELOADER_STARTUP_EXPLAINABLE_ERROR);
509
+ e.setPreloaderCommand(getPreloaderCommandString());
510
+ annotatePreloaderException(e, details.debugDir);
511
+ throw e;
512
+ } catch (const SystemException &e) {
513
+ throwPreloaderSpawnException("An error occurred while starting up "
514
+ "the preloader. It tried to report an error message, but "
515
+ "an I/O error occurred while reading this error message: " +
516
+ e.sys(),
517
+ SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
518
+ details);
519
+ } catch (const TimeoutException &) {
520
+ throwPreloaderSpawnException("An error occurred while starting up "
521
+ "the preloader. It tried to report an error message, but "
522
+ "it took too much time doing that.",
523
+ SpawnException::PRELOADER_STARTUP_TIMEOUT,
524
+ details);
525
+ }
526
+ }
527
+
528
+ void handleInvalidResponseType(StartupDetails &details, const string &line) {
529
+ throwPreloaderSpawnException("An error occurred while starting up "
530
+ "the preloader. It sent an unknown response type \"" +
531
+ cEscapeString(line) + "\".",
532
+ SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
533
+ details);
534
+ }
535
+
536
+ string negotiatePreloaderStartup(StartupDetails &details) {
537
+ TRACE_POINT();
538
+ string result;
539
+ try {
540
+ result = readMessageLine(details);
541
+ } catch (const SystemException &e) {
542
+ throwPreloaderSpawnException("An error occurred while starting up "
543
+ "the preloader. There was an I/O error while reading its "
544
+ "handshake message: " + e.sys(),
545
+ SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
546
+ details);
547
+ } catch (const TimeoutException &) {
548
+ throwPreloaderSpawnException("An error occurred while starting up "
549
+ "the preloader: it did not write a handshake message in time.",
550
+ SpawnException::PRELOADER_STARTUP_TIMEOUT,
551
+ details);
552
+ }
553
+
554
+ if (result == "I have control 1.0\n") {
555
+ UPDATE_TRACE_POINT();
556
+ sendStartupRequest(details);
557
+ try {
558
+ result = readMessageLine(details);
559
+ } catch (const SystemException &e) {
560
+ throwPreloaderSpawnException("An error occurred while starting up "
561
+ "the preloader. There was an I/O error while reading its "
562
+ "startup response: " + e.sys(),
563
+ SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
564
+ details);
565
+ } catch (const TimeoutException &) {
566
+ throwPreloaderSpawnException("An error occurred while starting up "
567
+ "the preloader: it did not write a startup response in time.",
568
+ SpawnException::PRELOADER_STARTUP_TIMEOUT,
569
+ details);
570
+ }
571
+ if (result == "Ready\n") {
572
+ return handleStartupResponse(details);
573
+ } else if (result == "Error\n") {
574
+ handleErrorResponse(details);
575
+ } else {
576
+ handleInvalidResponseType(details, result);
577
+ }
578
+ } else {
579
+ UPDATE_TRACE_POINT();
580
+ if (result == "Error\n") {
581
+ handleErrorResponse(details);
582
+ } else {
583
+ handleInvalidResponseType(details, result);
584
+ }
585
+ }
586
+
587
+ // Never reached, shut up compiler warning.
588
+ abort();
589
+ return "";
590
+ }
591
+
592
+ SpawnResult sendSpawnCommand(const Options &options) {
593
+ TRACE_POINT();
594
+ FileDescriptor fd;
595
+ try {
596
+ fd = connectToServer(socketAddress);
597
+ } catch (const SystemException &e) {
598
+ BackgroundIOCapturerPtr stderrCapturer;
599
+ throwPreloaderSpawnException("An error occurred while starting "
600
+ "the application. Unable to connect to the preloader's "
601
+ "socket: " + string(e.what()),
602
+ SpawnException::APP_STARTUP_PROTOCOL_ERROR,
603
+ stderrCapturer,
604
+ DebugDirPtr());
605
+ }
606
+
607
+ UPDATE_TRACE_POINT();
608
+ BufferedIO io(fd);
609
+ unsigned long long timeout = options.startTimeout * 1000;
610
+ string result;
611
+ vector<string> args;
612
+ vector<string>::const_iterator it;
613
+
614
+ writeExact(fd, "spawn\n", &timeout);
615
+ options.toVector(args, resourceLocator);
616
+ for (it = args.begin(); it != args.end(); it++) {
617
+ const string &key = *it;
618
+ it++;
619
+ const string &value = *it;
620
+ writeExact(fd, key + ": " + value + "\n", &timeout);
621
+ }
622
+ writeExact(fd, "\n", &timeout);
623
+
624
+ result = io.readLine(1024, &timeout);
625
+ if (result == "OK\n") {
626
+ UPDATE_TRACE_POINT();
627
+ pid_t spawnedPid;
628
+
629
+ spawnedPid = atoi(io.readLine(1024, &timeout).c_str());
630
+ if (spawnedPid <= 0) {
631
+ BackgroundIOCapturerPtr stderrCapturer;
632
+ throwPreloaderSpawnException("An error occurred while starting "
633
+ "the web application. Its preloader responded to the "
634
+ "'spawn' command with an invalid PID: '" +
635
+ toString(spawnedPid) + "'",
636
+ SpawnException::APP_STARTUP_PROTOCOL_ERROR,
637
+ stderrCapturer,
638
+ DebugDirPtr());
639
+ }
640
+ // TODO: we really should be checking UID.
641
+ // FIXME: for Passenger 4 we *must* check the UID otherwise this is a gaping security hole.
642
+ if (getsid(spawnedPid) != getsid(pid)) {
643
+ BackgroundIOCapturerPtr stderrCapturer;
644
+ throwPreloaderSpawnException("An error occurred while starting "
645
+ "the web application. Its preloader responded to the "
646
+ "'spawn' command with a PID that doesn't belong to "
647
+ "the same session: '" + toString(spawnedPid) + "'",
648
+ SpawnException::APP_STARTUP_PROTOCOL_ERROR,
649
+ stderrCapturer,
650
+ DebugDirPtr());
651
+ }
652
+
653
+ SpawnResult result;
654
+ result.pid = spawnedPid;
655
+ result.adminSocket = fd;
656
+ result.io = io;
657
+ return result;
658
+
659
+ } else if (result == "Error\n") {
660
+ UPDATE_TRACE_POINT();
661
+ NegotiationDetails details;
662
+ details.io = io;
663
+ details.timeout = timeout;
664
+ handleSpawnErrorResponse(details);
665
+
666
+ } else {
667
+ UPDATE_TRACE_POINT();
668
+ NegotiationDetails details;
669
+ handleInvalidSpawnResponseType(result, details);
670
+ }
671
+
672
+ return SpawnResult(); // Never reached.
673
+ }
674
+
675
+ template<typename Exception>
676
+ SpawnResult sendSpawnCommandAgain(const Exception &e, const Options &options) {
677
+ TRACE_POINT();
678
+ P_WARN("An error occurred while spawning a process: " << e.what());
679
+ P_WARN("The application preloader seems to have crashed, restarting it and trying again...");
680
+ stopPreloader();
681
+ startPreloader();
682
+ ScopeGuard guard(boost::bind(&SmartSpawner::stopPreloader, this));
683
+ SpawnResult result = sendSpawnCommand(options);
684
+ guard.clear();
685
+ return result;
686
+ }
687
+
688
+ protected:
689
+ virtual void annotateAppSpawnException(SpawnException &e, NegotiationDetails &details) {
690
+ Spawner::annotateAppSpawnException(e, details);
691
+ e.addAnnotations(preloaderAnnotations);
692
+ }
693
+
694
+ public:
695
+ SmartSpawner(const SafeLibevPtr &_libev,
696
+ const ResourceLocator &_resourceLocator,
697
+ const ServerInstanceDir::GenerationPtr &_generation,
698
+ const vector<string> &_preloaderCommand,
699
+ const Options &_options,
700
+ const SpawnerConfigPtr &_config = SpawnerConfigPtr())
701
+ : Spawner(_resourceLocator),
702
+ libev(_libev),
703
+ preloaderCommand(_preloaderCommand)
704
+ {
705
+ if (preloaderCommand.size() < 2) {
706
+ throw ArgumentException("preloaderCommand must have at least 2 elements");
707
+ }
708
+
709
+ generation = _generation;
710
+ options = _options.copyAndPersist().clearLogger();
711
+ pid = -1;
712
+ m_lastUsed = SystemTime::getUsec();
713
+
714
+ if (_config == NULL) {
715
+ config = make_shared<SpawnerConfig>();
716
+ } else {
717
+ config = _config;
718
+ }
719
+ }
720
+
721
+ virtual ~SmartSpawner() {
722
+ lock_guard<boost::mutex> l(syncher);
723
+ stopPreloader();
724
+ }
725
+
726
+ virtual ProcessPtr spawn(const Options &options) {
727
+ TRACE_POINT();
728
+ assert(options.appType == this->options.appType);
729
+ assert(options.appRoot == this->options.appRoot);
730
+
731
+ P_DEBUG("Spawning new process: appRoot=" << options.appRoot);
732
+ possiblyRaiseInternalError(options);
733
+
734
+ {
735
+ lock_guard<boost::mutex> l(simpleFieldSyncher);
736
+ m_lastUsed = SystemTime::getUsec();
737
+ }
738
+ UPDATE_TRACE_POINT();
739
+ lock_guard<boost::mutex> l(syncher);
740
+ if (!preloaderStarted()) {
741
+ UPDATE_TRACE_POINT();
742
+ startPreloader();
743
+ }
744
+
745
+ UPDATE_TRACE_POINT();
746
+ SpawnResult result;
747
+ try {
748
+ result = sendSpawnCommand(options);
749
+ } catch (const SystemException &e) {
750
+ result = sendSpawnCommandAgain(e, options);
751
+ } catch (const IOException &e) {
752
+ result = sendSpawnCommandAgain(e, options);
753
+ } catch (const SpawnException &e) {
754
+ result = sendSpawnCommandAgain(e, options);
755
+ }
756
+
757
+ UPDATE_TRACE_POINT();
758
+ NegotiationDetails details;
759
+ details.preparation = &preparation;
760
+ details.libev = libev;
761
+ details.pid = result.pid;
762
+ details.adminSocket = result.adminSocket;
763
+ details.io = result.io;
764
+ details.options = &options;
765
+ details.forwardStderr = config->forwardStderr;
766
+ ProcessPtr process = negotiateSpawn(details);
767
+ P_DEBUG("Process spawning done: appRoot=" << options.appRoot <<
768
+ ", pid=" << process->pid);
769
+ return process;
770
+ }
771
+
772
+ virtual bool cleanable() const {
773
+ return true;
774
+ }
775
+
776
+ virtual void cleanup() {
777
+ TRACE_POINT();
778
+ {
779
+ lock_guard<boost::mutex> l(simpleFieldSyncher);
780
+ m_lastUsed = SystemTime::getUsec();
781
+ }
782
+ lock_guard<boost::mutex> lock(syncher);
783
+ stopPreloader();
784
+ }
785
+
786
+ virtual unsigned long long lastUsed() const {
787
+ lock_guard<boost::mutex> lock(simpleFieldSyncher);
788
+ return m_lastUsed;
789
+ }
790
+
791
+ pid_t getPreloaderPid() const {
792
+ lock_guard<boost::mutex> lock(simpleFieldSyncher);
793
+ return pid;
794
+ }
795
+ };
796
+
797
+
798
+ } // namespace ApplicationPool2
799
+ } // namespace Passenger
800
+
801
+ #endif /* _PASSENGER_APPLICATION_POOL2_SMART_SPAWNER_H_ */