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.
- data/.travis.yml +3 -0
- data/NEWS +77 -7
- data/README.md +3 -11
- data/bin/passenger-install-apache2-module +24 -20
- data/bin/passenger-install-nginx-module +25 -23
- data/build/agents.rb +11 -0
- data/build/apache2.rb +9 -5
- data/build/basics.rb +37 -30
- data/build/common_library.rb +4 -1
- data/build/cplusplus_support.rb +5 -5
- data/build/cxx_tests.rb +28 -8
- data/build/integration_tests.rb +6 -3
- data/build/nginx.rb +3 -3
- data/build/packaging.rb +95 -57
- data/build/ruby_extension.rb +34 -21
- data/build/ruby_tests.rb +4 -2
- data/build/test_basics.rb +1 -1
- data/dev/run_travis.sh +36 -1
- data/doc/Users guide Apache.html +425 -308
- data/doc/Users guide Apache.idmap.txt +78 -70
- data/doc/Users guide Apache.index.sqlite3 +0 -0
- data/doc/Users guide Apache.txt +33 -92
- data/doc/Users guide Nginx.html +519 -220
- data/doc/Users guide Nginx.idmap.txt +78 -60
- data/doc/Users guide Nginx.txt +115 -26
- data/doc/Users guide Standalone.html +8 -2
- data/doc/users_guide_snippets/analysis_and_system_maintenance.txt +1 -7
- data/doc/users_guide_snippets/installation.txt +167 -22
- data/doc/users_guide_snippets/rackup_specifications.txt +4 -0
- data/doc/users_guide_snippets/since_version.txt +1 -0
- data/doc/users_guide_snippets/support_information.txt +3 -7
- data/doc/users_guide_snippets/tips.txt +0 -24
- data/ext/apache2/Configuration.cpp +11 -33
- data/ext/apache2/Configuration.hpp +3 -18
- data/ext/apache2/DirectoryMapper.h +20 -70
- data/ext/apache2/Hooks.cpp +2 -2
- data/ext/common/AgentsStarter.cpp +0 -2
- data/ext/common/AgentsStarter.h +0 -1
- data/ext/common/AgentsStarter.hpp +1 -3
- data/ext/common/ApplicationPool2/AppTypes.cpp +74 -0
- data/ext/common/ApplicationPool2/AppTypes.h +202 -0
- data/ext/common/ApplicationPool2/Common.h +12 -10
- data/ext/common/ApplicationPool2/DirectSpawner.h +256 -0
- data/ext/common/ApplicationPool2/DummySpawner.h +90 -0
- data/ext/common/ApplicationPool2/Group.h +311 -94
- data/ext/common/ApplicationPool2/Implementation.cpp +405 -145
- data/ext/common/ApplicationPool2/Options.h +24 -26
- data/ext/common/ApplicationPool2/PipeWatcher.h +20 -13
- data/ext/common/ApplicationPool2/Pool.h +326 -183
- data/ext/common/ApplicationPool2/Process.h +205 -55
- data/ext/common/ApplicationPool2/README.md +1 -1
- data/ext/common/ApplicationPool2/Session.h +21 -10
- data/ext/common/ApplicationPool2/SmartSpawner.h +801 -0
- data/ext/common/ApplicationPool2/Spawner.h +141 -1149
- data/ext/common/ApplicationPool2/SpawnerFactory.h +132 -0
- data/ext/common/ApplicationPool2/SuperGroup.h +146 -223
- data/ext/common/Constants.h +4 -2
- data/ext/common/Exceptions.h +23 -1
- data/ext/common/Logging.cpp +17 -6
- data/ext/common/Logging.h +37 -7
- data/ext/common/ResourceLocator.h +1 -1
- data/ext/common/Utils.cpp +49 -1
- data/ext/common/Utils.h +13 -4
- data/ext/common/{AnsiColorConstants.h → Utils/AnsiColorConstants.h} +0 -0
- data/ext/common/{BCrypt.cpp → Utils/BCrypt.cpp} +0 -0
- data/ext/common/{BCrypt.h → Utils/BCrypt.h} +0 -0
- data/ext/common/{Blowfish.c → Utils/Blowfish.c} +0 -0
- data/ext/common/{Blowfish.h → Utils/Blowfish.h} +0 -0
- data/ext/common/Utils/CachedFileStat.hpp +27 -25
- data/ext/common/Utils/Curl.h +184 -0
- data/ext/common/{HttpConstants.h → Utils/HttpConstants.h} +3 -0
- data/ext/common/Utils/IOUtils.cpp +6 -2
- data/ext/common/{IniFile.h → Utils/IniFile.h} +0 -0
- data/ext/common/Utils/LargeFiles.cpp +30 -0
- data/ext/common/Utils/LargeFiles.h +40 -0
- data/ext/common/Utils/StrIntUtils.cpp +72 -8
- data/ext/common/Utils/StrIntUtils.h +24 -2
- data/ext/common/Utils/StringMap.h +12 -2
- data/ext/common/Utils/VariantMap.h +51 -2
- data/ext/common/Utils/jsoncpp.cpp +1 -1
- data/ext/common/agents/Base.cpp +147 -11
- data/ext/common/agents/HelperAgent/AgentOptions.h +14 -6
- data/ext/common/agents/HelperAgent/Main.cpp +79 -19
- data/ext/common/agents/HelperAgent/RequestHandler.h +36 -16
- data/ext/common/agents/LoggingAgent/LoggingServer.h +3 -5
- data/ext/common/agents/LoggingAgent/Main.cpp +2 -4
- data/ext/common/agents/LoggingAgent/RemoteSender.h +18 -24
- data/ext/common/agents/SpawnPreparer.cpp +7 -0
- data/ext/common/agents/Watchdog/Main.cpp +96 -38
- data/ext/nginx/Configuration.c +26 -22
- data/ext/nginx/Configuration.h +4 -2
- data/ext/nginx/ContentHandler.c +23 -52
- data/ext/nginx/ContentHandler.h +5 -11
- data/ext/nginx/config +10 -3
- data/ext/nginx/ngx_http_passenger_module.c +21 -6
- data/ext/nginx/ngx_http_passenger_module.h +4 -1
- data/ext/oxt/dynamic_thread_group.hpp +9 -1
- data/ext/oxt/system_calls.cpp +2 -2
- data/ext/ruby/extconf.rb +2 -1
- data/helper-scripts/backtrace-sanitizer.rb +2 -0
- data/helper-scripts/wsgi-loader.py +54 -21
- data/lib/phusion_passenger.rb +5 -3
- data/lib/phusion_passenger/abstract_installer.rb +18 -41
- data/lib/phusion_passenger/admin_tools/memory_stats.rb +2 -2
- data/lib/phusion_passenger/admin_tools/server_instance.rb +2 -2
- data/lib/phusion_passenger/common_library.rb +23 -3
- data/lib/phusion_passenger/debug_logging.rb +10 -3
- data/lib/phusion_passenger/packaging.rb +1 -0
- data/lib/phusion_passenger/platform_info.rb +113 -115
- data/lib/phusion_passenger/platform_info/compiler.rb +224 -134
- data/lib/phusion_passenger/platform_info/cxx_portability.rb +143 -0
- data/lib/phusion_passenger/platform_info/depcheck.rb +371 -0
- data/lib/phusion_passenger/platform_info/depcheck_specs/apache2.rb +124 -0
- data/lib/phusion_passenger/platform_info/depcheck_specs/compiler_toolchain.rb +97 -0
- data/lib/phusion_passenger/platform_info/depcheck_specs/gems.rb +39 -0
- data/lib/phusion_passenger/platform_info/depcheck_specs/libs.rb +118 -0
- data/lib/phusion_passenger/platform_info/depcheck_specs/ruby.rb +137 -0
- data/lib/phusion_passenger/platform_info/depcheck_specs/utilities.rb +15 -0
- data/lib/phusion_passenger/platform_info/operating_system.rb +6 -5
- data/lib/phusion_passenger/platform_info/ruby.rb +45 -34
- data/lib/phusion_passenger/request_handler.rb +35 -22
- data/lib/phusion_passenger/request_handler/thread_handler.rb +5 -6
- data/lib/phusion_passenger/ruby_core_enhancements.rb +7 -1
- data/lib/phusion_passenger/standalone/runtime_installer.rb +43 -34
- data/lib/phusion_passenger/utils/robust_interruption.rb +34 -18
- data/passenger.gemspec +25 -0
- data/resources/templates/standalone/config.erb +3 -1
- data/test/config.json.travis +2 -2
- data/test/cxx/ApplicationPool2/DirectSpawnerTest.cpp +37 -5
- data/test/cxx/ApplicationPool2/PoolTest.cpp +143 -50
- data/test/cxx/ApplicationPool2/ProcessTest.cpp +8 -0
- data/test/cxx/ApplicationPool2/SmartSpawnerTest.cpp +28 -17
- data/test/cxx/ApplicationPool2/SpawnerTestCases.cpp +31 -26
- data/test/cxx/RequestHandlerTest.cpp +17 -1
- data/test/cxx/UtilsTest.cpp +84 -10
- data/test/integration_tests/apache2_tests.rb +49 -163
- data/test/integration_tests/hello_world_wsgi_spec.rb +2 -2
- data/test/integration_tests/mycook_spec.rb +1 -1
- data/test/integration_tests/nginx_tests.rb +37 -19
- data/test/ruby/request_handler_spec.rb +1 -0
- data/test/ruby/spec_helper.rb +52 -1
- data/test/stub/nginx/nginx.conf.erb +2 -0
- data/test/stub/rack/start.rb +5 -0
- data/test/stub/rails3.0/Gemfile.lock +30 -30
- data/test/stub/rails3.1/Gemfile +1 -1
- data/test/stub/rails3.1/Gemfile.lock +3 -3
- data/test/stub/rails3.2/Gemfile +1 -1
- data/test/stub/rails3.2/Gemfile.lock +4 -4
- data/test/stub/rails_apps/2.3/mycook/app/controllers/welcome_controller.rb +1 -1
- data/test/stub/rails_apps/2.3/mycook/app/helpers/recipes_helper.rb +2 -0
- data/test/stub/rails_apps/2.3/mycook/app/helpers/test_helper.rb +2 -0
- data/test/stub/rails_apps/2.3/mycook/app/helpers/uploads_helper.rb +2 -0
- data/test/stub/rails_apps/2.3/mycook/app/helpers/welcome_helper.rb +2 -0
- data/test/support/nginx_controller.rb +2 -1
- metadata +160 -156
- data/build/gempackagetask.rb +0 -99
- data/build/packagetask.rb +0 -186
- data/ext/common/StringListCreator.h +0 -83
- 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_ */
|