passenger 5.0.9 → 5.0.10
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.
- checksums.yaml +8 -8
- checksums.yaml.gz.asc +7 -7
- data.tar.gz.asc +7 -7
- data/CHANGELOG +15 -0
- data/CONTRIBUTORS +6 -0
- data/README.md +1 -1
- data/bin/passenger-install-apache2-module +24 -11
- data/bin/passenger-status +29 -14
- data/build/agents.rb +12 -10
- data/build/cxx_tests.rb +30 -30
- data/doc/Design and Architecture.html +1 -10
- data/doc/Design and Architecture.txt +1 -6
- data/doc/Users guide Apache.html +1 -19
- data/doc/Users guide Apache.txt +1 -1
- data/doc/Users guide Nginx.html +2 -20
- data/doc/Users guide Nginx.txt +2 -2
- data/doc/users_guide_snippets/tips.txt +0 -9
- data/ext/common/ApplicationPool2/ApiKey.h +158 -0
- data/ext/common/ApplicationPool2/BasicGroupInfo.h +81 -0
- data/ext/common/ApplicationPool2/BasicProcessInfo.h +106 -0
- data/ext/common/ApplicationPool2/Common.h +5 -44
- data/ext/common/ApplicationPool2/Context.h +94 -0
- data/ext/common/ApplicationPool2/Group.h +130 -1205
- data/ext/common/ApplicationPool2/Group/InitializationAndShutdown.cpp +190 -0
- data/ext/common/ApplicationPool2/Group/InternalUtils.cpp +329 -0
- data/ext/common/ApplicationPool2/Group/LifetimeAndBasics.cpp +103 -0
- data/ext/common/ApplicationPool2/{Pool/Debug.h → Group/Miscellaneous.cpp} +40 -38
- data/ext/common/ApplicationPool2/Group/OutOfBandWork.cpp +323 -0
- data/ext/common/ApplicationPool2/Group/ProcessListManagement.cpp +606 -0
- data/ext/common/ApplicationPool2/Group/SessionManagement.cpp +337 -0
- data/ext/common/ApplicationPool2/Group/SpawningAndRestarting.cpp +478 -0
- data/ext/common/ApplicationPool2/Group/StateInspection.cpp +197 -0
- data/ext/common/ApplicationPool2/Group/Verification.cpp +159 -0
- data/ext/common/ApplicationPool2/Implementation.cpp +19 -1401
- data/ext/common/ApplicationPool2/Options.h +5 -5
- data/ext/common/ApplicationPool2/Pool.h +260 -815
- data/ext/common/ApplicationPool2/Pool/{AnalyticsCollection.h → AnalyticsCollection.cpp} +55 -56
- data/ext/common/ApplicationPool2/Pool/{GarbageCollection.h → GarbageCollection.cpp} +49 -49
- data/ext/common/ApplicationPool2/Pool/GeneralUtils.cpp +241 -0
- data/ext/common/ApplicationPool2/Pool/GroupUtils.cpp +276 -0
- data/ext/common/ApplicationPool2/Pool/InitializationAndShutdown.cpp +145 -0
- data/ext/common/ApplicationPool2/Pool/Miscellaneous.cpp +244 -0
- data/ext/common/ApplicationPool2/Pool/ProcessUtils.cpp +330 -0
- data/ext/common/ApplicationPool2/Pool/StateInspection.cpp +299 -0
- data/ext/common/ApplicationPool2/Process.h +399 -205
- data/ext/common/ApplicationPool2/Session.h +70 -28
- data/ext/common/ApplicationPool2/Socket.h +1 -0
- data/ext/common/Constants.h +11 -3
- data/ext/common/Exceptions.h +1 -1
- data/ext/common/Logging.cpp +9 -4
- data/ext/common/Logging.h +6 -0
- data/ext/common/ServerKit/HttpServer.h +225 -215
- data/ext/common/ServerKit/Server.h +57 -57
- data/ext/common/SpawningKit/BackgroundIOCapturer.h +160 -0
- data/ext/common/SpawningKit/Config.h +107 -0
- data/ext/common/{ApplicationPool2 → SpawningKit}/DirectSpawner.h +17 -16
- data/ext/common/{ApplicationPool2 → SpawningKit}/DummySpawner.h +33 -33
- data/ext/common/{ApplicationPool2/SpawnerFactory.h → SpawningKit/Factory.h} +17 -17
- data/ext/common/{ApplicationPool2/ComponentInfo.h → SpawningKit/Options.h} +8 -21
- data/ext/common/SpawningKit/PipeWatcher.h +148 -0
- data/ext/common/{ApplicationPool2/PipeWatcher.h → SpawningKit/Result.h} +15 -33
- data/ext/common/{ApplicationPool2 → SpawningKit}/SmartSpawner.h +52 -57
- data/ext/common/{ApplicationPool2 → SpawningKit}/Spawner.h +83 -371
- data/ext/common/SpawningKit/UserSwitchingRules.h +265 -0
- data/ext/common/Utils/BufferedIO.h +24 -0
- data/ext/common/{ApplicationPool2/SpawnObject.h → Utils/ClassUtils.h} +24 -51
- data/ext/common/Utils/IOUtils.cpp +70 -0
- data/ext/common/Utils/IOUtils.h +19 -0
- data/ext/common/Utils/JsonUtils.h +113 -0
- data/ext/common/Utils/StrIntUtils.h +29 -0
- data/ext/common/Utils/json.h +1 -1
- data/ext/common/agents/ApiServerUtils.h +941 -0
- data/ext/common/agents/HelperAgent/{AdminServer.h → ApiServer.h} +163 -365
- data/ext/common/agents/HelperAgent/Main.cpp +86 -88
- data/ext/common/agents/HelperAgent/OptionParser.h +9 -10
- data/ext/common/agents/HelperAgent/RequestHandler/BufferBody.cpp +3 -0
- data/ext/common/agents/HelperAgent/RequestHandler/ForwardResponse.cpp +2 -0
- data/ext/common/agents/HelperAgent/RequestHandler/Hooks.cpp +1 -1
- data/ext/common/agents/HelperAgent/RequestHandler/SendRequest.cpp +2 -2
- data/ext/common/agents/LoggingAgent/ApiServer.h +279 -0
- data/ext/common/agents/LoggingAgent/Main.cpp +41 -51
- data/ext/common/agents/LoggingAgent/OptionParser.h +11 -11
- data/ext/common/agents/Watchdog/ApiServer.h +311 -0
- data/ext/common/agents/Watchdog/Main.cpp +91 -65
- data/helper-scripts/prespawn +2 -0
- data/lib/phusion_passenger.rb +1 -1
- data/lib/phusion_passenger/admin_tools/instance.rb +1 -1
- data/lib/phusion_passenger/common_library.rb +27 -14
- data/lib/phusion_passenger/config/{admin_command_command.rb → api_call_command.rb} +19 -16
- data/lib/phusion_passenger/config/detach_process_command.rb +6 -3
- data/lib/phusion_passenger/config/main.rb +3 -5
- data/lib/phusion_passenger/config/reopen_logs_command.rb +29 -7
- data/lib/phusion_passenger/config/restart_app_command.rb +13 -4
- data/lib/phusion_passenger/config/utils.rb +15 -8
- data/lib/phusion_passenger/constants.rb +6 -2
- data/lib/phusion_passenger/platform_info/apache.rb +4 -0
- data/lib/phusion_passenger/platform_info/apache_detector.rb +18 -3
- data/resources/templates/apache2/mpm_unknown.txt.erb +20 -0
- metadata +42 -21
- metadata.gz.asc +7 -7
- data/ext/common/ApplicationPool2/Pool/GeneralUtils.h +0 -127
- data/ext/common/ApplicationPool2/Pool/Inspection.h +0 -219
- data/ext/common/ApplicationPool2/Pool/ProcessUtils.h +0 -85
- data/ext/common/ApplicationPool2/SuperGroup.h +0 -706
- data/ext/common/agents/LoggingAgent/AdminServer.h +0 -435
- data/ext/common/agents/Watchdog/AdminServer.h +0 -432
@@ -0,0 +1,337 @@
|
|
1
|
+
/*
|
2
|
+
* Phusion Passenger - https://www.phusionpassenger.com/
|
3
|
+
* Copyright (c) 2011-2015 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
|
+
#include <ApplicationPool2/Group.h>
|
26
|
+
|
27
|
+
/*************************************************************************
|
28
|
+
*
|
29
|
+
* Session management functions for ApplicationPool2::Group
|
30
|
+
*
|
31
|
+
*************************************************************************/
|
32
|
+
|
33
|
+
namespace Passenger {
|
34
|
+
namespace ApplicationPool2 {
|
35
|
+
|
36
|
+
using namespace std;
|
37
|
+
using namespace boost;
|
38
|
+
|
39
|
+
|
40
|
+
/****************************
|
41
|
+
*
|
42
|
+
* Private methods
|
43
|
+
*
|
44
|
+
****************************/
|
45
|
+
|
46
|
+
|
47
|
+
/* Determines which process to route a get() action to. The returned process
|
48
|
+
* is guaranteed to be `canBeRoutedTo()`, i.e. not totally busy.
|
49
|
+
*
|
50
|
+
* A request is routed to an enabled processes, or if there are none,
|
51
|
+
* from a disabling process. The rationale is as follows:
|
52
|
+
* If there are no enabled process, then waiting for one to spawn is too
|
53
|
+
* expensive. The next best thing is to route to disabling processes
|
54
|
+
* until more processes have been spawned.
|
55
|
+
*/
|
56
|
+
Group::RouteResult
|
57
|
+
Group::route(const Options &options) const {
|
58
|
+
if (OXT_LIKELY(enabledCount > 0)) {
|
59
|
+
if (options.stickySessionId == 0) {
|
60
|
+
Process *process = findEnabledProcessWithLowestBusyness();
|
61
|
+
if (process->canBeRoutedTo()) {
|
62
|
+
return RouteResult(process);
|
63
|
+
} else {
|
64
|
+
return RouteResult(NULL, true);
|
65
|
+
}
|
66
|
+
} else {
|
67
|
+
Process *process = findProcessWithStickySessionIdOrLowestBusyness(
|
68
|
+
options.stickySessionId);
|
69
|
+
if (process != NULL) {
|
70
|
+
if (process->canBeRoutedTo()) {
|
71
|
+
return RouteResult(process);
|
72
|
+
} else {
|
73
|
+
return RouteResult(NULL, false);
|
74
|
+
}
|
75
|
+
} else {
|
76
|
+
return RouteResult(NULL, true);
|
77
|
+
}
|
78
|
+
}
|
79
|
+
} else {
|
80
|
+
Process *process = findProcessWithLowestBusyness(disablingProcesses);
|
81
|
+
if (process->canBeRoutedTo()) {
|
82
|
+
return RouteResult(process);
|
83
|
+
} else {
|
84
|
+
return RouteResult(NULL, true);
|
85
|
+
}
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
89
|
+
SessionPtr
|
90
|
+
Group::newSession(Process *process, unsigned long long now) {
|
91
|
+
bool wasTotallyBusy = process->isTotallyBusy();
|
92
|
+
SessionPtr session = process->newSession(now);
|
93
|
+
session->onInitiateFailure = _onSessionInitiateFailure;
|
94
|
+
session->onClose = _onSessionClose;
|
95
|
+
if (process->enabled == Process::ENABLED) {
|
96
|
+
enabledProcessBusynessLevels[process->getIndex()] = process->busyness();
|
97
|
+
if (!wasTotallyBusy && process->isTotallyBusy()) {
|
98
|
+
nEnabledProcessesTotallyBusy++;
|
99
|
+
}
|
100
|
+
}
|
101
|
+
return session;
|
102
|
+
}
|
103
|
+
|
104
|
+
void
|
105
|
+
Group::_onSessionInitiateFailure(Session *session) {
|
106
|
+
Process *process = session->getProcess();
|
107
|
+
assert(process != NULL);
|
108
|
+
process->getGroup()->onSessionInitiateFailure(process, session);
|
109
|
+
}
|
110
|
+
|
111
|
+
void
|
112
|
+
Group::_onSessionClose(Session *session) {
|
113
|
+
Process *process = session->getProcess();
|
114
|
+
assert(process != NULL);
|
115
|
+
process->getGroup()->onSessionClose(process, session);
|
116
|
+
}
|
117
|
+
|
118
|
+
OXT_FORCE_INLINE void
|
119
|
+
Group::onSessionInitiateFailure(Process *process, Session *session) {
|
120
|
+
boost::container::vector<Callback> actions;
|
121
|
+
|
122
|
+
TRACE_POINT();
|
123
|
+
// Standard resource management boilerplate stuff...
|
124
|
+
Pool *pool = getPool();
|
125
|
+
boost::unique_lock<boost::mutex> lock(pool->syncher);
|
126
|
+
assert(process->isAlive());
|
127
|
+
assert(isAlive() || getLifeStatus() == SHUTTING_DOWN);
|
128
|
+
|
129
|
+
UPDATE_TRACE_POINT();
|
130
|
+
P_DEBUG("Could not initiate a session with process " <<
|
131
|
+
process->inspect() << ", detaching from pool if possible");
|
132
|
+
if (!pool->detachProcessUnlocked(process->shared_from_this(), actions)) {
|
133
|
+
P_DEBUG("Process was already detached");
|
134
|
+
}
|
135
|
+
pool->fullVerifyInvariants();
|
136
|
+
lock.unlock();
|
137
|
+
runAllActions(actions);
|
138
|
+
}
|
139
|
+
|
140
|
+
OXT_FORCE_INLINE void
|
141
|
+
Group::onSessionClose(Process *process, Session *session) {
|
142
|
+
TRACE_POINT();
|
143
|
+
// Standard resource management boilerplate stuff...
|
144
|
+
Pool *pool = getPool();
|
145
|
+
boost::unique_lock<boost::mutex> lock(pool->syncher);
|
146
|
+
assert(process->isAlive());
|
147
|
+
assert(isAlive() || getLifeStatus() == SHUTTING_DOWN);
|
148
|
+
|
149
|
+
P_TRACE(2, "Session closed for process " << process->inspect());
|
150
|
+
verifyInvariants();
|
151
|
+
UPDATE_TRACE_POINT();
|
152
|
+
|
153
|
+
/* Update statistics. */
|
154
|
+
bool wasTotallyBusy = process->isTotallyBusy();
|
155
|
+
process->sessionClosed(session);
|
156
|
+
assert(process->getLifeStatus() == Process::ALIVE);
|
157
|
+
assert(process->enabled == Process::ENABLED
|
158
|
+
|| process->enabled == Process::DISABLING
|
159
|
+
|| process->enabled == Process::DETACHED);
|
160
|
+
if (process->enabled == Process::ENABLED) {
|
161
|
+
enabledProcessBusynessLevels[process->getIndex()] = process->busyness();
|
162
|
+
if (wasTotallyBusy) {
|
163
|
+
assert(nEnabledProcessesTotallyBusy >= 1);
|
164
|
+
nEnabledProcessesTotallyBusy--;
|
165
|
+
}
|
166
|
+
}
|
167
|
+
|
168
|
+
/* This group now has a process that's guaranteed to be not
|
169
|
+
* totally busy.
|
170
|
+
*/
|
171
|
+
assert(!process->isTotallyBusy());
|
172
|
+
|
173
|
+
bool detachingBecauseOfMaxRequests = false;
|
174
|
+
bool detachingBecauseCapacityNeeded = false;
|
175
|
+
bool shouldDetach =
|
176
|
+
( detachingBecauseOfMaxRequests = (
|
177
|
+
options.maxRequests > 0
|
178
|
+
&& process->processed >= options.maxRequests
|
179
|
+
)) || (
|
180
|
+
detachingBecauseCapacityNeeded = (
|
181
|
+
process->sessions == 0
|
182
|
+
&& getWaitlist.empty()
|
183
|
+
&& (
|
184
|
+
!pool->getWaitlist.empty()
|
185
|
+
|| anotherGroupIsWaitingForCapacity()
|
186
|
+
)
|
187
|
+
)
|
188
|
+
);
|
189
|
+
bool shouldDisable =
|
190
|
+
process->enabled == Process::DISABLING
|
191
|
+
&& process->sessions == 0
|
192
|
+
&& enabledCount > 0;
|
193
|
+
|
194
|
+
if (shouldDetach || shouldDisable) {
|
195
|
+
UPDATE_TRACE_POINT();
|
196
|
+
boost::container::vector<Callback> actions;
|
197
|
+
|
198
|
+
if (shouldDetach) {
|
199
|
+
if (detachingBecauseCapacityNeeded) {
|
200
|
+
/* Someone might be trying to get() a session for a different
|
201
|
+
* group that couldn't be spawned because of lack of pool capacity.
|
202
|
+
* If this group isn't under sufficiently load (as apparent by the
|
203
|
+
* checked conditions) then now's a good time to detach
|
204
|
+
* this process or group in order to free capacity.
|
205
|
+
*/
|
206
|
+
P_DEBUG("Process " << process->inspect() << " is no longer totally "
|
207
|
+
"busy; detaching it in order to make room in the pool");
|
208
|
+
} else {
|
209
|
+
/* This process has processed its maximum number of requests,
|
210
|
+
* so we detach it.
|
211
|
+
*/
|
212
|
+
P_DEBUG("Process " << process->inspect() <<
|
213
|
+
" has reached its maximum number of requests (" <<
|
214
|
+
options.maxRequests << "); detaching it");
|
215
|
+
}
|
216
|
+
pool->detachProcessUnlocked(process->shared_from_this(), actions);
|
217
|
+
} else {
|
218
|
+
ProcessPtr processPtr = process->shared_from_this();
|
219
|
+
removeProcessFromList(processPtr, disablingProcesses);
|
220
|
+
addProcessToList(processPtr, disabledProcesses);
|
221
|
+
removeFromDisableWaitlist(processPtr, DR_SUCCESS, actions);
|
222
|
+
maybeInitiateOobw(process);
|
223
|
+
}
|
224
|
+
|
225
|
+
pool->fullVerifyInvariants();
|
226
|
+
lock.unlock();
|
227
|
+
runAllActions(actions);
|
228
|
+
|
229
|
+
} else {
|
230
|
+
UPDATE_TRACE_POINT();
|
231
|
+
|
232
|
+
// This could change process->enabled.
|
233
|
+
maybeInitiateOobw(process);
|
234
|
+
|
235
|
+
if (!getWaitlist.empty() && process->enabled == Process::ENABLED) {
|
236
|
+
/* If there are clients on this group waiting for a process to
|
237
|
+
* become available then call them now.
|
238
|
+
*/
|
239
|
+
UPDATE_TRACE_POINT();
|
240
|
+
// Already calls verifyInvariants().
|
241
|
+
assignSessionsToGetWaitersQuickly(lock);
|
242
|
+
}
|
243
|
+
}
|
244
|
+
}
|
245
|
+
|
246
|
+
|
247
|
+
/****************************
|
248
|
+
*
|
249
|
+
* Public methods
|
250
|
+
*
|
251
|
+
****************************/
|
252
|
+
|
253
|
+
|
254
|
+
SessionPtr
|
255
|
+
Group::get(const Options &newOptions, const GetCallback &callback,
|
256
|
+
boost::container::vector<Callback> &postLockActions)
|
257
|
+
{
|
258
|
+
assert(isAlive());
|
259
|
+
|
260
|
+
if (OXT_LIKELY(!restarting())) {
|
261
|
+
if (OXT_UNLIKELY(needsRestart(newOptions))) {
|
262
|
+
restart(newOptions);
|
263
|
+
} else {
|
264
|
+
mergeOptions(newOptions);
|
265
|
+
}
|
266
|
+
if (OXT_UNLIKELY(!newOptions.noop && shouldSpawnForGetAction())) {
|
267
|
+
// If we're trying to spawn the first process for this group, and
|
268
|
+
// spawning failed because the pool is at full capacity, then we
|
269
|
+
// try to kill some random idle process in the pool and try again.
|
270
|
+
if (spawn() == SR_ERR_POOL_AT_FULL_CAPACITY && enabledCount == 0) {
|
271
|
+
P_INFO("Unable to spawn the the sole process for group " << info.name <<
|
272
|
+
" because the max pool size has been reached. Trying " <<
|
273
|
+
"to shutdown another idle process to free capacity...");
|
274
|
+
if (poolForceFreeCapacity(this, postLockActions) != NULL) {
|
275
|
+
SpawnResult result = spawn();
|
276
|
+
assert(result == SR_OK);
|
277
|
+
(void) result;
|
278
|
+
} else {
|
279
|
+
P_INFO("There are no processes right now that are eligible "
|
280
|
+
"for shutdown. Will try again later.");
|
281
|
+
}
|
282
|
+
}
|
283
|
+
}
|
284
|
+
}
|
285
|
+
|
286
|
+
if (OXT_UNLIKELY(newOptions.noop)) {
|
287
|
+
return nullProcess->createSessionObject((Socket *) NULL);
|
288
|
+
}
|
289
|
+
|
290
|
+
if (OXT_UNLIKELY(enabledCount == 0)) {
|
291
|
+
/* We don't have any processes yet, but they're on the way.
|
292
|
+
*
|
293
|
+
* We have some choices here. If there are disabling processes
|
294
|
+
* then we generally want to use them, except:
|
295
|
+
* - When non-rolling restarting because those disabling processes
|
296
|
+
* are from the old version.
|
297
|
+
* - When all disabling processes are totally busy.
|
298
|
+
*
|
299
|
+
* Whenever a disabling process cannot be used, call the callback
|
300
|
+
* after a process has been spawned or has failed to spawn, or
|
301
|
+
* when a disabling process becomes available.
|
302
|
+
*/
|
303
|
+
assert(m_spawning || restarting() || poolAtFullCapacity());
|
304
|
+
|
305
|
+
if (disablingCount > 0 && !restarting()) {
|
306
|
+
Process *process = findProcessWithLowestBusyness(disablingProcesses);
|
307
|
+
assert(process != NULL);
|
308
|
+
if (!process->isTotallyBusy()) {
|
309
|
+
return newSession(process, newOptions.currentTime);
|
310
|
+
}
|
311
|
+
}
|
312
|
+
|
313
|
+
if (pushGetWaiter(newOptions, callback, postLockActions)) {
|
314
|
+
P_DEBUG("No session checked out yet: group is spawning or restarting");
|
315
|
+
}
|
316
|
+
return SessionPtr();
|
317
|
+
} else {
|
318
|
+
RouteResult result = route(newOptions);
|
319
|
+
if (result.process == NULL) {
|
320
|
+
/* Looks like all processes are totally busy.
|
321
|
+
* Wait until a new one has been spawned or until
|
322
|
+
* resources have become free.
|
323
|
+
*/
|
324
|
+
if (pushGetWaiter(newOptions, callback, postLockActions)) {
|
325
|
+
P_DEBUG("No session checked out yet: all processes are at full capacity");
|
326
|
+
}
|
327
|
+
return SessionPtr();
|
328
|
+
} else {
|
329
|
+
P_DEBUG("Session checked out from process " << result.process->inspect());
|
330
|
+
return newSession(result.process, newOptions.currentTime);
|
331
|
+
}
|
332
|
+
}
|
333
|
+
}
|
334
|
+
|
335
|
+
|
336
|
+
} // namespace ApplicationPool2
|
337
|
+
} // namespace Passenger
|
@@ -0,0 +1,478 @@
|
|
1
|
+
/*
|
2
|
+
* Phusion Passenger - https://www.phusionpassenger.com/
|
3
|
+
* Copyright (c) 2011-2015 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
|
+
#include <ApplicationPool2/Group.h>
|
26
|
+
|
27
|
+
/*************************************************************************
|
28
|
+
*
|
29
|
+
* Session management functions for ApplicationPool2::Group
|
30
|
+
*
|
31
|
+
*************************************************************************/
|
32
|
+
|
33
|
+
namespace Passenger {
|
34
|
+
namespace ApplicationPool2 {
|
35
|
+
|
36
|
+
using namespace std;
|
37
|
+
using namespace boost;
|
38
|
+
|
39
|
+
|
40
|
+
/****************************
|
41
|
+
*
|
42
|
+
* Private methods
|
43
|
+
*
|
44
|
+
****************************/
|
45
|
+
|
46
|
+
|
47
|
+
// The 'self' parameter is for keeping the current Group object alive while this thread is running.
|
48
|
+
void
|
49
|
+
Group::spawnThreadMain(GroupPtr self, SpawningKit::SpawnerPtr spawner,
|
50
|
+
Options options, unsigned int restartsInitiated)
|
51
|
+
{
|
52
|
+
spawnThreadRealMain(spawner, options, restartsInitiated);
|
53
|
+
}
|
54
|
+
|
55
|
+
void
|
56
|
+
Group::spawnThreadRealMain(const SpawningKit::SpawnerPtr &spawner,
|
57
|
+
const Options &options, unsigned int restartsInitiated)
|
58
|
+
{
|
59
|
+
TRACE_POINT();
|
60
|
+
this_thread::disable_interruption di;
|
61
|
+
this_thread::disable_syscall_interruption dsi;
|
62
|
+
|
63
|
+
Pool *pool = getPool();
|
64
|
+
Pool::DebugSupportPtr debug = pool->debugSupport;
|
65
|
+
|
66
|
+
bool done = false;
|
67
|
+
while (!done) {
|
68
|
+
bool shouldFail = false;
|
69
|
+
if (debug != NULL && debug->spawning) {
|
70
|
+
UPDATE_TRACE_POINT();
|
71
|
+
this_thread::restore_interruption ri(di);
|
72
|
+
this_thread::restore_syscall_interruption rsi(dsi);
|
73
|
+
this_thread::interruption_point();
|
74
|
+
string iteration;
|
75
|
+
{
|
76
|
+
LockGuard g(debug->syncher);
|
77
|
+
debug->spawnLoopIteration++;
|
78
|
+
iteration = toString(debug->spawnLoopIteration);
|
79
|
+
}
|
80
|
+
P_DEBUG("Begin spawn loop iteration " << iteration);
|
81
|
+
debug->debugger->send("Begin spawn loop iteration " +
|
82
|
+
iteration);
|
83
|
+
|
84
|
+
vector<string> cases;
|
85
|
+
cases.push_back("Proceed with spawn loop iteration " + iteration);
|
86
|
+
cases.push_back("Fail spawn loop iteration " + iteration);
|
87
|
+
MessagePtr message = debug->messages->recvAny(cases);
|
88
|
+
shouldFail = message->name == "Fail spawn loop iteration " + iteration;
|
89
|
+
}
|
90
|
+
|
91
|
+
ProcessPtr process;
|
92
|
+
ExceptionPtr exception;
|
93
|
+
try {
|
94
|
+
UPDATE_TRACE_POINT();
|
95
|
+
this_thread::restore_interruption ri(di);
|
96
|
+
this_thread::restore_syscall_interruption rsi(dsi);
|
97
|
+
if (shouldFail) {
|
98
|
+
SpawnException e("Simulated failure");
|
99
|
+
processAndLogNewSpawnException(e, options, pool->getSpawningKitConfig());
|
100
|
+
throw e;
|
101
|
+
} else {
|
102
|
+
process = createProcessObject(spawner->spawn(options));
|
103
|
+
}
|
104
|
+
} catch (const thread_interrupted &) {
|
105
|
+
break;
|
106
|
+
} catch (const tracable_exception &e) {
|
107
|
+
exception = copyException(e);
|
108
|
+
// Let other (unexpected) exceptions crash the program so
|
109
|
+
// gdb can generate a backtrace.
|
110
|
+
}
|
111
|
+
|
112
|
+
UPDATE_TRACE_POINT();
|
113
|
+
ScopeGuard guard(boost::bind(Process::forceTriggerShutdownAndCleanup, process));
|
114
|
+
boost::unique_lock<boost::mutex> lock(pool->syncher);
|
115
|
+
|
116
|
+
if (!isAlive()) {
|
117
|
+
if (process != NULL) {
|
118
|
+
P_DEBUG("Group is being shut down so dropping process " <<
|
119
|
+
process->inspect() << " which we just spawned and exiting spawn loop");
|
120
|
+
} else {
|
121
|
+
P_DEBUG("The group is being shut down. A process failed "
|
122
|
+
"to be spawned anyway, so ignoring this error and exiting "
|
123
|
+
"spawn loop");
|
124
|
+
}
|
125
|
+
// We stop immediately because any previously assumed invariants
|
126
|
+
// may have been violated.
|
127
|
+
break;
|
128
|
+
} else if (restartsInitiated != this->restartsInitiated) {
|
129
|
+
if (process != NULL) {
|
130
|
+
P_DEBUG("A restart was issued for the group, so dropping process " <<
|
131
|
+
process->inspect() << " which we just spawned and exiting spawn loop");
|
132
|
+
} else {
|
133
|
+
P_DEBUG("A restart was issued for the group. A process failed "
|
134
|
+
"to be spawned anyway, so ignoring this error and exiting "
|
135
|
+
"spawn loop");
|
136
|
+
}
|
137
|
+
// We stop immediately because any previously assumed invariants
|
138
|
+
// may have been violated.
|
139
|
+
break;
|
140
|
+
}
|
141
|
+
|
142
|
+
verifyInvariants();
|
143
|
+
assert(m_spawning);
|
144
|
+
assert(processesBeingSpawned > 0);
|
145
|
+
|
146
|
+
processesBeingSpawned--;
|
147
|
+
assert(processesBeingSpawned == 0);
|
148
|
+
|
149
|
+
UPDATE_TRACE_POINT();
|
150
|
+
boost::container::vector<Callback> actions;
|
151
|
+
if (process != NULL) {
|
152
|
+
AttachResult result = attach(process, actions);
|
153
|
+
if (result == AR_OK) {
|
154
|
+
guard.clear();
|
155
|
+
if (getWaitlist.empty()) {
|
156
|
+
pool->assignSessionsToGetWaiters(actions);
|
157
|
+
} else {
|
158
|
+
assignSessionsToGetWaiters(actions);
|
159
|
+
}
|
160
|
+
P_DEBUG("New process count = " << enabledCount <<
|
161
|
+
", remaining get waiters = " << getWaitlist.size());
|
162
|
+
} else {
|
163
|
+
done = true;
|
164
|
+
P_DEBUG("Unable to attach spawned process " << process->inspect());
|
165
|
+
if (result == AR_ANOTHER_GROUP_IS_WAITING_FOR_CAPACITY) {
|
166
|
+
pool->possiblySpawnMoreProcessesForExistingGroups();
|
167
|
+
}
|
168
|
+
}
|
169
|
+
} else {
|
170
|
+
// TODO: sure this is the best thing? if there are
|
171
|
+
// processes currently alive we should just use them.
|
172
|
+
if (enabledCount == 0) {
|
173
|
+
enableAllDisablingProcesses(actions);
|
174
|
+
}
|
175
|
+
Pool::assignExceptionToGetWaiters(getWaitlist, exception, actions);
|
176
|
+
pool->assignSessionsToGetWaiters(actions);
|
177
|
+
done = true;
|
178
|
+
}
|
179
|
+
|
180
|
+
done = done
|
181
|
+
|| (processLowerLimitsSatisfied() && getWaitlist.empty())
|
182
|
+
|| processUpperLimitsReached()
|
183
|
+
|| pool->atFullCapacityUnlocked();
|
184
|
+
m_spawning = !done;
|
185
|
+
if (done) {
|
186
|
+
P_DEBUG("Spawn loop done");
|
187
|
+
} else {
|
188
|
+
processesBeingSpawned++;
|
189
|
+
P_DEBUG("Continue spawning");
|
190
|
+
}
|
191
|
+
|
192
|
+
UPDATE_TRACE_POINT();
|
193
|
+
pool->fullVerifyInvariants();
|
194
|
+
lock.unlock();
|
195
|
+
UPDATE_TRACE_POINT();
|
196
|
+
runAllActions(actions);
|
197
|
+
UPDATE_TRACE_POINT();
|
198
|
+
}
|
199
|
+
|
200
|
+
if (debug != NULL && debug->spawning) {
|
201
|
+
debug->debugger->send("Spawn loop done");
|
202
|
+
}
|
203
|
+
}
|
204
|
+
|
205
|
+
// The 'self' parameter is for keeping the current Group object alive while this thread is running.
|
206
|
+
void
|
207
|
+
Group::finalizeRestart(GroupPtr self,
|
208
|
+
Options oldOptions,
|
209
|
+
Options newOptions, RestartMethod method,
|
210
|
+
SpawningKit::FactoryPtr spawningKitFactory,
|
211
|
+
unsigned int restartsInitiated,
|
212
|
+
boost::container::vector<Callback> postLockActions)
|
213
|
+
{
|
214
|
+
TRACE_POINT();
|
215
|
+
|
216
|
+
Pool::runAllActions(postLockActions);
|
217
|
+
postLockActions.clear();
|
218
|
+
|
219
|
+
this_thread::disable_interruption di;
|
220
|
+
this_thread::disable_syscall_interruption dsi;
|
221
|
+
|
222
|
+
// Create a new spawner.
|
223
|
+
Options spawnerOptions = oldOptions;
|
224
|
+
resetOptions(newOptions, &spawnerOptions);
|
225
|
+
SpawningKit::SpawnerPtr newSpawner = spawningKitFactory->create(spawnerOptions);
|
226
|
+
SpawningKit::SpawnerPtr oldSpawner;
|
227
|
+
|
228
|
+
UPDATE_TRACE_POINT();
|
229
|
+
Pool *pool = getPool();
|
230
|
+
|
231
|
+
Pool::DebugSupportPtr debug = pool->debugSupport;
|
232
|
+
if (debug != NULL && debug->restarting) {
|
233
|
+
this_thread::restore_interruption ri(di);
|
234
|
+
this_thread::restore_syscall_interruption rsi(dsi);
|
235
|
+
this_thread::interruption_point();
|
236
|
+
debug->debugger->send("About to end restarting");
|
237
|
+
debug->messages->recv("Finish restarting");
|
238
|
+
}
|
239
|
+
|
240
|
+
ScopedLock l(pool->syncher);
|
241
|
+
if (!isAlive()) {
|
242
|
+
P_DEBUG("Group " << getName() << " is shutting down, so aborting restart");
|
243
|
+
return;
|
244
|
+
}
|
245
|
+
if (restartsInitiated != this->restartsInitiated) {
|
246
|
+
// Before this restart could be finalized, another restart command was given.
|
247
|
+
// The spawner we just created might be out of date now so we abort.
|
248
|
+
P_DEBUG("Restart of group " << getName() << " aborted because a new restart was initiated concurrently");
|
249
|
+
if (debug != NULL && debug->restarting) {
|
250
|
+
debug->debugger->send("Restarting aborted");
|
251
|
+
}
|
252
|
+
return;
|
253
|
+
}
|
254
|
+
|
255
|
+
// Run some sanity checks.
|
256
|
+
pool->fullVerifyInvariants();
|
257
|
+
assert(m_restarting);
|
258
|
+
UPDATE_TRACE_POINT();
|
259
|
+
|
260
|
+
// Atomically swap the new spawner with the old one.
|
261
|
+
resetOptions(newOptions);
|
262
|
+
oldSpawner = spawner;
|
263
|
+
spawner = newSpawner;
|
264
|
+
|
265
|
+
m_restarting = false;
|
266
|
+
if (shouldSpawn()) {
|
267
|
+
spawn();
|
268
|
+
} else if (isWaitingForCapacity()) {
|
269
|
+
P_INFO("Group " << getName() << " is waiting for capacity to become available. "
|
270
|
+
"Trying to shutdown another idle process to free capacity...");
|
271
|
+
if (pool->forceFreeCapacity(this, postLockActions) != NULL) {
|
272
|
+
spawn();
|
273
|
+
} else {
|
274
|
+
P_INFO("There are no processes right now that are eligible "
|
275
|
+
"for shutdown. Will try again later.");
|
276
|
+
}
|
277
|
+
}
|
278
|
+
verifyInvariants();
|
279
|
+
|
280
|
+
l.unlock();
|
281
|
+
oldSpawner.reset();
|
282
|
+
Pool::runAllActions(postLockActions);
|
283
|
+
P_DEBUG("Restart of group " << getName() << " done");
|
284
|
+
if (debug != NULL && debug->restarting) {
|
285
|
+
debug->debugger->send("Restarting done");
|
286
|
+
}
|
287
|
+
}
|
288
|
+
|
289
|
+
|
290
|
+
/****************************
|
291
|
+
*
|
292
|
+
* Public methods
|
293
|
+
*
|
294
|
+
****************************/
|
295
|
+
|
296
|
+
|
297
|
+
void
|
298
|
+
Group::restart(const Options &options, RestartMethod method) {
|
299
|
+
boost::container::vector<Callback> actions;
|
300
|
+
|
301
|
+
assert(isAlive());
|
302
|
+
P_DEBUG("Restarting group " << getName());
|
303
|
+
|
304
|
+
// If there is currently a restarter thread or a spawner thread active,
|
305
|
+
// the following tells them to abort their current work as soon as possible.
|
306
|
+
restartsInitiated++;
|
307
|
+
|
308
|
+
processesBeingSpawned = 0;
|
309
|
+
m_spawning = false;
|
310
|
+
m_restarting = true;
|
311
|
+
uuid = generateUuid(pool);
|
312
|
+
detachAll(actions);
|
313
|
+
getPool()->interruptableThreads.create_thread(
|
314
|
+
boost::bind(&Group::finalizeRestart, this, shared_from_this(),
|
315
|
+
this->options.copyAndPersist().clearPerRequestFields(),
|
316
|
+
options.copyAndPersist().clearPerRequestFields(),
|
317
|
+
method, getContext()->getSpawningKitFactory(),
|
318
|
+
restartsInitiated, actions),
|
319
|
+
"Group restarter: " + getName(),
|
320
|
+
POOL_HELPER_THREAD_STACK_SIZE
|
321
|
+
);
|
322
|
+
}
|
323
|
+
|
324
|
+
bool
|
325
|
+
Group::restarting() const {
|
326
|
+
return m_restarting;
|
327
|
+
}
|
328
|
+
|
329
|
+
bool
|
330
|
+
Group::needsRestart(const Options &options) {
|
331
|
+
if (m_restarting) {
|
332
|
+
return false;
|
333
|
+
} else {
|
334
|
+
time_t now;
|
335
|
+
struct stat buf;
|
336
|
+
|
337
|
+
if (options.currentTime != 0) {
|
338
|
+
now = options.currentTime / 1000000;
|
339
|
+
} else {
|
340
|
+
now = SystemTime::get();
|
341
|
+
}
|
342
|
+
|
343
|
+
if (lastRestartFileCheckTime == 0) {
|
344
|
+
// First time we call needsRestart() for this group.
|
345
|
+
if (syscalls::stat(restartFile.c_str(), &buf) == 0) {
|
346
|
+
lastRestartFileMtime = buf.st_mtime;
|
347
|
+
} else {
|
348
|
+
lastRestartFileMtime = 0;
|
349
|
+
}
|
350
|
+
lastRestartFileCheckTime = now;
|
351
|
+
return false;
|
352
|
+
|
353
|
+
} else if (lastRestartFileCheckTime <= now - (time_t) options.statThrottleRate) {
|
354
|
+
// Not first time we call needsRestart() for this group.
|
355
|
+
// Stat throttle time has passed.
|
356
|
+
bool restart;
|
357
|
+
|
358
|
+
lastRestartFileCheckTime = now;
|
359
|
+
|
360
|
+
if (lastRestartFileMtime > 0) {
|
361
|
+
// restart.txt existed before
|
362
|
+
if (syscalls::stat(restartFile.c_str(), &buf) == -1) {
|
363
|
+
// restart.txt no longer exists
|
364
|
+
lastRestartFileMtime = buf.st_mtime;
|
365
|
+
restart = false;
|
366
|
+
} else if (buf.st_mtime != lastRestartFileMtime) {
|
367
|
+
// restart.txt's mtime has changed
|
368
|
+
lastRestartFileMtime = buf.st_mtime;
|
369
|
+
restart = true;
|
370
|
+
} else {
|
371
|
+
restart = false;
|
372
|
+
}
|
373
|
+
} else {
|
374
|
+
// restart.txt didn't exist before
|
375
|
+
if (syscalls::stat(restartFile.c_str(), &buf) == 0) {
|
376
|
+
// restart.txt now exists
|
377
|
+
lastRestartFileMtime = buf.st_mtime;
|
378
|
+
restart = true;
|
379
|
+
} else {
|
380
|
+
// restart.txt still doesn't exist
|
381
|
+
lastRestartFileMtime = 0;
|
382
|
+
restart = false;
|
383
|
+
}
|
384
|
+
}
|
385
|
+
|
386
|
+
if (!restart) {
|
387
|
+
alwaysRestartFileExists = restart =
|
388
|
+
syscalls::stat(alwaysRestartFile.c_str(), &buf) == 0;
|
389
|
+
}
|
390
|
+
|
391
|
+
return restart;
|
392
|
+
|
393
|
+
} else {
|
394
|
+
// Not first time we call needsRestart() for this group.
|
395
|
+
// Still within stat throttling window.
|
396
|
+
if (alwaysRestartFileExists) {
|
397
|
+
// always_restart.txt existed before
|
398
|
+
alwaysRestartFileExists = syscalls::stat(
|
399
|
+
alwaysRestartFile.c_str(), &buf) == 0;
|
400
|
+
return alwaysRestartFileExists;
|
401
|
+
} else {
|
402
|
+
// Don't check until stat throttling window is over
|
403
|
+
return false;
|
404
|
+
}
|
405
|
+
}
|
406
|
+
}
|
407
|
+
}
|
408
|
+
|
409
|
+
/**
|
410
|
+
* Attempts to increase the number of processes by one, while respecting the
|
411
|
+
* resource limits. That is, this method will ensure that there are at least
|
412
|
+
* `minProcesses` processes, but no more than `maxProcesses` processes, and no
|
413
|
+
* more than `pool->max` processes in the entire pool.
|
414
|
+
*/
|
415
|
+
SpawnResult
|
416
|
+
Group::spawn() {
|
417
|
+
assert(isAlive());
|
418
|
+
if (m_spawning) {
|
419
|
+
return SR_IN_PROGRESS;
|
420
|
+
} else if (restarting()) {
|
421
|
+
return SR_ERR_RESTARTING;
|
422
|
+
} else if (processUpperLimitsReached()) {
|
423
|
+
return SR_ERR_GROUP_UPPER_LIMITS_REACHED;
|
424
|
+
} else if (poolAtFullCapacity()) {
|
425
|
+
return SR_ERR_POOL_AT_FULL_CAPACITY;
|
426
|
+
} else {
|
427
|
+
P_DEBUG("Requested spawning of new process for group " << info.name);
|
428
|
+
interruptableThreads.create_thread(
|
429
|
+
boost::bind(&Group::spawnThreadMain,
|
430
|
+
this, shared_from_this(), spawner,
|
431
|
+
options.copyAndPersist().clearPerRequestFields(),
|
432
|
+
restartsInitiated),
|
433
|
+
"Group process spawner: " + info.name,
|
434
|
+
POOL_HELPER_THREAD_STACK_SIZE);
|
435
|
+
m_spawning = true;
|
436
|
+
processesBeingSpawned++;
|
437
|
+
return SR_OK;
|
438
|
+
}
|
439
|
+
}
|
440
|
+
|
441
|
+
bool
|
442
|
+
Group::spawning() const {
|
443
|
+
return m_spawning;
|
444
|
+
}
|
445
|
+
|
446
|
+
/** Whether a new process should be spawned for this group. */
|
447
|
+
bool
|
448
|
+
Group::shouldSpawn() const {
|
449
|
+
return allowSpawn()
|
450
|
+
&& (
|
451
|
+
!processLowerLimitsSatisfied()
|
452
|
+
|| allEnabledProcessesAreTotallyBusy()
|
453
|
+
|| !getWaitlist.empty()
|
454
|
+
);
|
455
|
+
}
|
456
|
+
|
457
|
+
/** Whether a new process should be spawned for this group in the
|
458
|
+
* specific case that another get action is to be performed.
|
459
|
+
*/
|
460
|
+
bool
|
461
|
+
Group::shouldSpawnForGetAction() const {
|
462
|
+
return enabledCount == 0 || shouldSpawn();
|
463
|
+
}
|
464
|
+
|
465
|
+
/**
|
466
|
+
* Whether a new process is allowed to be spawned for this group,
|
467
|
+
* i.e. whether the upper processes limits have not been reached.
|
468
|
+
*/
|
469
|
+
bool
|
470
|
+
Group::allowSpawn() const {
|
471
|
+
return isAlive()
|
472
|
+
&& !processUpperLimitsReached()
|
473
|
+
&& !poolAtFullCapacity();
|
474
|
+
}
|
475
|
+
|
476
|
+
|
477
|
+
} // namespace ApplicationPool2
|
478
|
+
} // namespace Passenger
|