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.

Files changed (106) hide show
  1. checksums.yaml +8 -8
  2. checksums.yaml.gz.asc +7 -7
  3. data.tar.gz.asc +7 -7
  4. data/CHANGELOG +15 -0
  5. data/CONTRIBUTORS +6 -0
  6. data/README.md +1 -1
  7. data/bin/passenger-install-apache2-module +24 -11
  8. data/bin/passenger-status +29 -14
  9. data/build/agents.rb +12 -10
  10. data/build/cxx_tests.rb +30 -30
  11. data/doc/Design and Architecture.html +1 -10
  12. data/doc/Design and Architecture.txt +1 -6
  13. data/doc/Users guide Apache.html +1 -19
  14. data/doc/Users guide Apache.txt +1 -1
  15. data/doc/Users guide Nginx.html +2 -20
  16. data/doc/Users guide Nginx.txt +2 -2
  17. data/doc/users_guide_snippets/tips.txt +0 -9
  18. data/ext/common/ApplicationPool2/ApiKey.h +158 -0
  19. data/ext/common/ApplicationPool2/BasicGroupInfo.h +81 -0
  20. data/ext/common/ApplicationPool2/BasicProcessInfo.h +106 -0
  21. data/ext/common/ApplicationPool2/Common.h +5 -44
  22. data/ext/common/ApplicationPool2/Context.h +94 -0
  23. data/ext/common/ApplicationPool2/Group.h +130 -1205
  24. data/ext/common/ApplicationPool2/Group/InitializationAndShutdown.cpp +190 -0
  25. data/ext/common/ApplicationPool2/Group/InternalUtils.cpp +329 -0
  26. data/ext/common/ApplicationPool2/Group/LifetimeAndBasics.cpp +103 -0
  27. data/ext/common/ApplicationPool2/{Pool/Debug.h → Group/Miscellaneous.cpp} +40 -38
  28. data/ext/common/ApplicationPool2/Group/OutOfBandWork.cpp +323 -0
  29. data/ext/common/ApplicationPool2/Group/ProcessListManagement.cpp +606 -0
  30. data/ext/common/ApplicationPool2/Group/SessionManagement.cpp +337 -0
  31. data/ext/common/ApplicationPool2/Group/SpawningAndRestarting.cpp +478 -0
  32. data/ext/common/ApplicationPool2/Group/StateInspection.cpp +197 -0
  33. data/ext/common/ApplicationPool2/Group/Verification.cpp +159 -0
  34. data/ext/common/ApplicationPool2/Implementation.cpp +19 -1401
  35. data/ext/common/ApplicationPool2/Options.h +5 -5
  36. data/ext/common/ApplicationPool2/Pool.h +260 -815
  37. data/ext/common/ApplicationPool2/Pool/{AnalyticsCollection.h → AnalyticsCollection.cpp} +55 -56
  38. data/ext/common/ApplicationPool2/Pool/{GarbageCollection.h → GarbageCollection.cpp} +49 -49
  39. data/ext/common/ApplicationPool2/Pool/GeneralUtils.cpp +241 -0
  40. data/ext/common/ApplicationPool2/Pool/GroupUtils.cpp +276 -0
  41. data/ext/common/ApplicationPool2/Pool/InitializationAndShutdown.cpp +145 -0
  42. data/ext/common/ApplicationPool2/Pool/Miscellaneous.cpp +244 -0
  43. data/ext/common/ApplicationPool2/Pool/ProcessUtils.cpp +330 -0
  44. data/ext/common/ApplicationPool2/Pool/StateInspection.cpp +299 -0
  45. data/ext/common/ApplicationPool2/Process.h +399 -205
  46. data/ext/common/ApplicationPool2/Session.h +70 -28
  47. data/ext/common/ApplicationPool2/Socket.h +1 -0
  48. data/ext/common/Constants.h +11 -3
  49. data/ext/common/Exceptions.h +1 -1
  50. data/ext/common/Logging.cpp +9 -4
  51. data/ext/common/Logging.h +6 -0
  52. data/ext/common/ServerKit/HttpServer.h +225 -215
  53. data/ext/common/ServerKit/Server.h +57 -57
  54. data/ext/common/SpawningKit/BackgroundIOCapturer.h +160 -0
  55. data/ext/common/SpawningKit/Config.h +107 -0
  56. data/ext/common/{ApplicationPool2 → SpawningKit}/DirectSpawner.h +17 -16
  57. data/ext/common/{ApplicationPool2 → SpawningKit}/DummySpawner.h +33 -33
  58. data/ext/common/{ApplicationPool2/SpawnerFactory.h → SpawningKit/Factory.h} +17 -17
  59. data/ext/common/{ApplicationPool2/ComponentInfo.h → SpawningKit/Options.h} +8 -21
  60. data/ext/common/SpawningKit/PipeWatcher.h +148 -0
  61. data/ext/common/{ApplicationPool2/PipeWatcher.h → SpawningKit/Result.h} +15 -33
  62. data/ext/common/{ApplicationPool2 → SpawningKit}/SmartSpawner.h +52 -57
  63. data/ext/common/{ApplicationPool2 → SpawningKit}/Spawner.h +83 -371
  64. data/ext/common/SpawningKit/UserSwitchingRules.h +265 -0
  65. data/ext/common/Utils/BufferedIO.h +24 -0
  66. data/ext/common/{ApplicationPool2/SpawnObject.h → Utils/ClassUtils.h} +24 -51
  67. data/ext/common/Utils/IOUtils.cpp +70 -0
  68. data/ext/common/Utils/IOUtils.h +19 -0
  69. data/ext/common/Utils/JsonUtils.h +113 -0
  70. data/ext/common/Utils/StrIntUtils.h +29 -0
  71. data/ext/common/Utils/json.h +1 -1
  72. data/ext/common/agents/ApiServerUtils.h +941 -0
  73. data/ext/common/agents/HelperAgent/{AdminServer.h → ApiServer.h} +163 -365
  74. data/ext/common/agents/HelperAgent/Main.cpp +86 -88
  75. data/ext/common/agents/HelperAgent/OptionParser.h +9 -10
  76. data/ext/common/agents/HelperAgent/RequestHandler/BufferBody.cpp +3 -0
  77. data/ext/common/agents/HelperAgent/RequestHandler/ForwardResponse.cpp +2 -0
  78. data/ext/common/agents/HelperAgent/RequestHandler/Hooks.cpp +1 -1
  79. data/ext/common/agents/HelperAgent/RequestHandler/SendRequest.cpp +2 -2
  80. data/ext/common/agents/LoggingAgent/ApiServer.h +279 -0
  81. data/ext/common/agents/LoggingAgent/Main.cpp +41 -51
  82. data/ext/common/agents/LoggingAgent/OptionParser.h +11 -11
  83. data/ext/common/agents/Watchdog/ApiServer.h +311 -0
  84. data/ext/common/agents/Watchdog/Main.cpp +91 -65
  85. data/helper-scripts/prespawn +2 -0
  86. data/lib/phusion_passenger.rb +1 -1
  87. data/lib/phusion_passenger/admin_tools/instance.rb +1 -1
  88. data/lib/phusion_passenger/common_library.rb +27 -14
  89. data/lib/phusion_passenger/config/{admin_command_command.rb → api_call_command.rb} +19 -16
  90. data/lib/phusion_passenger/config/detach_process_command.rb +6 -3
  91. data/lib/phusion_passenger/config/main.rb +3 -5
  92. data/lib/phusion_passenger/config/reopen_logs_command.rb +29 -7
  93. data/lib/phusion_passenger/config/restart_app_command.rb +13 -4
  94. data/lib/phusion_passenger/config/utils.rb +15 -8
  95. data/lib/phusion_passenger/constants.rb +6 -2
  96. data/lib/phusion_passenger/platform_info/apache.rb +4 -0
  97. data/lib/phusion_passenger/platform_info/apache_detector.rb +18 -3
  98. data/resources/templates/apache2/mpm_unknown.txt.erb +20 -0
  99. metadata +42 -21
  100. metadata.gz.asc +7 -7
  101. data/ext/common/ApplicationPool2/Pool/GeneralUtils.h +0 -127
  102. data/ext/common/ApplicationPool2/Pool/Inspection.h +0 -219
  103. data/ext/common/ApplicationPool2/Pool/ProcessUtils.h +0 -85
  104. data/ext/common/ApplicationPool2/SuperGroup.h +0 -706
  105. data/ext/common/agents/LoggingAgent/AdminServer.h +0 -435
  106. 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