passenger 3.9.2.beta → 4.0.0.rc4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of passenger might be problematic. Click here for more details.

Files changed (159) hide show
  1. data/.travis.yml +3 -0
  2. data/NEWS +77 -7
  3. data/README.md +3 -11
  4. data/bin/passenger-install-apache2-module +24 -20
  5. data/bin/passenger-install-nginx-module +25 -23
  6. data/build/agents.rb +11 -0
  7. data/build/apache2.rb +9 -5
  8. data/build/basics.rb +37 -30
  9. data/build/common_library.rb +4 -1
  10. data/build/cplusplus_support.rb +5 -5
  11. data/build/cxx_tests.rb +28 -8
  12. data/build/integration_tests.rb +6 -3
  13. data/build/nginx.rb +3 -3
  14. data/build/packaging.rb +95 -57
  15. data/build/ruby_extension.rb +34 -21
  16. data/build/ruby_tests.rb +4 -2
  17. data/build/test_basics.rb +1 -1
  18. data/dev/run_travis.sh +36 -1
  19. data/doc/Users guide Apache.html +425 -308
  20. data/doc/Users guide Apache.idmap.txt +78 -70
  21. data/doc/Users guide Apache.index.sqlite3 +0 -0
  22. data/doc/Users guide Apache.txt +33 -92
  23. data/doc/Users guide Nginx.html +519 -220
  24. data/doc/Users guide Nginx.idmap.txt +78 -60
  25. data/doc/Users guide Nginx.txt +115 -26
  26. data/doc/Users guide Standalone.html +8 -2
  27. data/doc/users_guide_snippets/analysis_and_system_maintenance.txt +1 -7
  28. data/doc/users_guide_snippets/installation.txt +167 -22
  29. data/doc/users_guide_snippets/rackup_specifications.txt +4 -0
  30. data/doc/users_guide_snippets/since_version.txt +1 -0
  31. data/doc/users_guide_snippets/support_information.txt +3 -7
  32. data/doc/users_guide_snippets/tips.txt +0 -24
  33. data/ext/apache2/Configuration.cpp +11 -33
  34. data/ext/apache2/Configuration.hpp +3 -18
  35. data/ext/apache2/DirectoryMapper.h +20 -70
  36. data/ext/apache2/Hooks.cpp +2 -2
  37. data/ext/common/AgentsStarter.cpp +0 -2
  38. data/ext/common/AgentsStarter.h +0 -1
  39. data/ext/common/AgentsStarter.hpp +1 -3
  40. data/ext/common/ApplicationPool2/AppTypes.cpp +74 -0
  41. data/ext/common/ApplicationPool2/AppTypes.h +202 -0
  42. data/ext/common/ApplicationPool2/Common.h +12 -10
  43. data/ext/common/ApplicationPool2/DirectSpawner.h +256 -0
  44. data/ext/common/ApplicationPool2/DummySpawner.h +90 -0
  45. data/ext/common/ApplicationPool2/Group.h +311 -94
  46. data/ext/common/ApplicationPool2/Implementation.cpp +405 -145
  47. data/ext/common/ApplicationPool2/Options.h +24 -26
  48. data/ext/common/ApplicationPool2/PipeWatcher.h +20 -13
  49. data/ext/common/ApplicationPool2/Pool.h +326 -183
  50. data/ext/common/ApplicationPool2/Process.h +205 -55
  51. data/ext/common/ApplicationPool2/README.md +1 -1
  52. data/ext/common/ApplicationPool2/Session.h +21 -10
  53. data/ext/common/ApplicationPool2/SmartSpawner.h +801 -0
  54. data/ext/common/ApplicationPool2/Spawner.h +141 -1149
  55. data/ext/common/ApplicationPool2/SpawnerFactory.h +132 -0
  56. data/ext/common/ApplicationPool2/SuperGroup.h +146 -223
  57. data/ext/common/Constants.h +4 -2
  58. data/ext/common/Exceptions.h +23 -1
  59. data/ext/common/Logging.cpp +17 -6
  60. data/ext/common/Logging.h +37 -7
  61. data/ext/common/ResourceLocator.h +1 -1
  62. data/ext/common/Utils.cpp +49 -1
  63. data/ext/common/Utils.h +13 -4
  64. data/ext/common/{AnsiColorConstants.h → Utils/AnsiColorConstants.h} +0 -0
  65. data/ext/common/{BCrypt.cpp → Utils/BCrypt.cpp} +0 -0
  66. data/ext/common/{BCrypt.h → Utils/BCrypt.h} +0 -0
  67. data/ext/common/{Blowfish.c → Utils/Blowfish.c} +0 -0
  68. data/ext/common/{Blowfish.h → Utils/Blowfish.h} +0 -0
  69. data/ext/common/Utils/CachedFileStat.hpp +27 -25
  70. data/ext/common/Utils/Curl.h +184 -0
  71. data/ext/common/{HttpConstants.h → Utils/HttpConstants.h} +3 -0
  72. data/ext/common/Utils/IOUtils.cpp +6 -2
  73. data/ext/common/{IniFile.h → Utils/IniFile.h} +0 -0
  74. data/ext/common/Utils/LargeFiles.cpp +30 -0
  75. data/ext/common/Utils/LargeFiles.h +40 -0
  76. data/ext/common/Utils/StrIntUtils.cpp +72 -8
  77. data/ext/common/Utils/StrIntUtils.h +24 -2
  78. data/ext/common/Utils/StringMap.h +12 -2
  79. data/ext/common/Utils/VariantMap.h +51 -2
  80. data/ext/common/Utils/jsoncpp.cpp +1 -1
  81. data/ext/common/agents/Base.cpp +147 -11
  82. data/ext/common/agents/HelperAgent/AgentOptions.h +14 -6
  83. data/ext/common/agents/HelperAgent/Main.cpp +79 -19
  84. data/ext/common/agents/HelperAgent/RequestHandler.h +36 -16
  85. data/ext/common/agents/LoggingAgent/LoggingServer.h +3 -5
  86. data/ext/common/agents/LoggingAgent/Main.cpp +2 -4
  87. data/ext/common/agents/LoggingAgent/RemoteSender.h +18 -24
  88. data/ext/common/agents/SpawnPreparer.cpp +7 -0
  89. data/ext/common/agents/Watchdog/Main.cpp +96 -38
  90. data/ext/nginx/Configuration.c +26 -22
  91. data/ext/nginx/Configuration.h +4 -2
  92. data/ext/nginx/ContentHandler.c +23 -52
  93. data/ext/nginx/ContentHandler.h +5 -11
  94. data/ext/nginx/config +10 -3
  95. data/ext/nginx/ngx_http_passenger_module.c +21 -6
  96. data/ext/nginx/ngx_http_passenger_module.h +4 -1
  97. data/ext/oxt/dynamic_thread_group.hpp +9 -1
  98. data/ext/oxt/system_calls.cpp +2 -2
  99. data/ext/ruby/extconf.rb +2 -1
  100. data/helper-scripts/backtrace-sanitizer.rb +2 -0
  101. data/helper-scripts/wsgi-loader.py +54 -21
  102. data/lib/phusion_passenger.rb +5 -3
  103. data/lib/phusion_passenger/abstract_installer.rb +18 -41
  104. data/lib/phusion_passenger/admin_tools/memory_stats.rb +2 -2
  105. data/lib/phusion_passenger/admin_tools/server_instance.rb +2 -2
  106. data/lib/phusion_passenger/common_library.rb +23 -3
  107. data/lib/phusion_passenger/debug_logging.rb +10 -3
  108. data/lib/phusion_passenger/packaging.rb +1 -0
  109. data/lib/phusion_passenger/platform_info.rb +113 -115
  110. data/lib/phusion_passenger/platform_info/compiler.rb +224 -134
  111. data/lib/phusion_passenger/platform_info/cxx_portability.rb +143 -0
  112. data/lib/phusion_passenger/platform_info/depcheck.rb +371 -0
  113. data/lib/phusion_passenger/platform_info/depcheck_specs/apache2.rb +124 -0
  114. data/lib/phusion_passenger/platform_info/depcheck_specs/compiler_toolchain.rb +97 -0
  115. data/lib/phusion_passenger/platform_info/depcheck_specs/gems.rb +39 -0
  116. data/lib/phusion_passenger/platform_info/depcheck_specs/libs.rb +118 -0
  117. data/lib/phusion_passenger/platform_info/depcheck_specs/ruby.rb +137 -0
  118. data/lib/phusion_passenger/platform_info/depcheck_specs/utilities.rb +15 -0
  119. data/lib/phusion_passenger/platform_info/operating_system.rb +6 -5
  120. data/lib/phusion_passenger/platform_info/ruby.rb +45 -34
  121. data/lib/phusion_passenger/request_handler.rb +35 -22
  122. data/lib/phusion_passenger/request_handler/thread_handler.rb +5 -6
  123. data/lib/phusion_passenger/ruby_core_enhancements.rb +7 -1
  124. data/lib/phusion_passenger/standalone/runtime_installer.rb +43 -34
  125. data/lib/phusion_passenger/utils/robust_interruption.rb +34 -18
  126. data/passenger.gemspec +25 -0
  127. data/resources/templates/standalone/config.erb +3 -1
  128. data/test/config.json.travis +2 -2
  129. data/test/cxx/ApplicationPool2/DirectSpawnerTest.cpp +37 -5
  130. data/test/cxx/ApplicationPool2/PoolTest.cpp +143 -50
  131. data/test/cxx/ApplicationPool2/ProcessTest.cpp +8 -0
  132. data/test/cxx/ApplicationPool2/SmartSpawnerTest.cpp +28 -17
  133. data/test/cxx/ApplicationPool2/SpawnerTestCases.cpp +31 -26
  134. data/test/cxx/RequestHandlerTest.cpp +17 -1
  135. data/test/cxx/UtilsTest.cpp +84 -10
  136. data/test/integration_tests/apache2_tests.rb +49 -163
  137. data/test/integration_tests/hello_world_wsgi_spec.rb +2 -2
  138. data/test/integration_tests/mycook_spec.rb +1 -1
  139. data/test/integration_tests/nginx_tests.rb +37 -19
  140. data/test/ruby/request_handler_spec.rb +1 -0
  141. data/test/ruby/spec_helper.rb +52 -1
  142. data/test/stub/nginx/nginx.conf.erb +2 -0
  143. data/test/stub/rack/start.rb +5 -0
  144. data/test/stub/rails3.0/Gemfile.lock +30 -30
  145. data/test/stub/rails3.1/Gemfile +1 -1
  146. data/test/stub/rails3.1/Gemfile.lock +3 -3
  147. data/test/stub/rails3.2/Gemfile +1 -1
  148. data/test/stub/rails3.2/Gemfile.lock +4 -4
  149. data/test/stub/rails_apps/2.3/mycook/app/controllers/welcome_controller.rb +1 -1
  150. data/test/stub/rails_apps/2.3/mycook/app/helpers/recipes_helper.rb +2 -0
  151. data/test/stub/rails_apps/2.3/mycook/app/helpers/test_helper.rb +2 -0
  152. data/test/stub/rails_apps/2.3/mycook/app/helpers/uploads_helper.rb +2 -0
  153. data/test/stub/rails_apps/2.3/mycook/app/helpers/welcome_helper.rb +2 -0
  154. data/test/support/nginx_controller.rb +2 -1
  155. metadata +160 -156
  156. data/build/gempackagetask.rb +0 -99
  157. data/build/packagetask.rb +0 -186
  158. data/ext/common/StringListCreator.h +0 -83
  159. data/lib/phusion_passenger/dependencies.rb +0 -657
@@ -0,0 +1,90 @@
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_DUMMY_SPAWNER_H_
26
+ #define _PASSENGER_APPLICATION_POOL2_DUMMY_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 DummySpawner: public Spawner {
39
+ private:
40
+ SpawnerConfigPtr config;
41
+ boost::mutex lock;
42
+ unsigned int count;
43
+
44
+ public:
45
+ unsigned int cleanCount;
46
+
47
+ DummySpawner(const ResourceLocator &resourceLocator, const SpawnerConfigPtr &_config)
48
+ : Spawner(resourceLocator),
49
+ config(_config)
50
+ {
51
+ count = 0;
52
+ cleanCount = 0;
53
+ }
54
+
55
+ virtual ProcessPtr spawn(const Options &options) {
56
+ TRACE_POINT();
57
+ possiblyRaiseInternalError(options);
58
+
59
+ SocketPair adminSocket = createUnixSocketPair();
60
+ SocketListPtr sockets = make_shared<SocketList>();
61
+ sockets->add("main", "tcp://127.0.0.1:1234", "session", config->concurrency);
62
+ syscalls::usleep(config->spawnTime);
63
+
64
+ lock_guard<boost::mutex> l(lock);
65
+ count++;
66
+ ProcessPtr process = make_shared<Process>(SafeLibevPtr(),
67
+ (pid_t) count, "gupid-" + toString(count),
68
+ toString(count),
69
+ adminSocket.second, FileDescriptor(), sockets,
70
+ SystemTime::getUsec(), SystemTime::getUsec());
71
+ process->dummy = true;
72
+ return process;
73
+ }
74
+
75
+ virtual bool cleanable() const {
76
+ return true;
77
+ }
78
+
79
+ virtual void cleanup() {
80
+ cleanCount++;
81
+ }
82
+ };
83
+
84
+ typedef shared_ptr<DummySpawner> DummySpawnerPtr;
85
+
86
+
87
+ } // namespace ApplicationPool2
88
+ } // namespace Passenger
89
+
90
+ #endif /* _PASSENGER_APPLICATION_POOL2_DUMMY_SPAWNER_H_ */
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * Phusion Passenger - https://www.phusionpassenger.com/
3
- * Copyright (c) 2011, 2012 Phusion
3
+ * Copyright (c) 2011-2013 Phusion
4
4
  *
5
5
  * "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
6
6
  *
@@ -31,14 +31,16 @@
31
31
  #include <deque>
32
32
  #include <boost/thread.hpp>
33
33
  #include <boost/bind.hpp>
34
+ #include <boost/foreach.hpp>
34
35
  #include <boost/shared_ptr.hpp>
35
36
  #include <boost/make_shared.hpp>
36
37
  #include <oxt/macros.hpp>
37
38
  #include <oxt/thread.hpp>
39
+ #include <oxt/dynamic_thread_group.hpp>
38
40
  #include <cassert>
39
41
  #include <ApplicationPool2/Common.h>
40
42
  #include <ApplicationPool2/ComponentInfo.h>
41
- #include <ApplicationPool2/Spawner.h>
43
+ #include <ApplicationPool2/SpawnerFactory.h>
42
44
  #include <ApplicationPool2/Process.h>
43
45
  #include <ApplicationPool2/Options.h>
44
46
  #include <Utils.h>
@@ -78,42 +80,107 @@ private:
78
80
  { }
79
81
  };
80
82
 
81
- mutable boost::mutex backrefSyncher;
83
+ /**
84
+ * Protects `m_shuttingDown`.
85
+ */
86
+ mutable boost::mutex lifetimeSyncher;
87
+ /**
88
+ * A back reference to the containing SuperGroup. Should never
89
+ * be NULL because a SuperGroup should outlive all its containing
90
+ * Groups.
91
+ * Read-only; only set during initialization.
92
+ */
82
93
  weak_ptr<SuperGroup> superGroup;
83
94
  CachedFileStat cstat;
84
95
  FileChangeChecker fileChangeChecker;
85
96
  string restartFile;
86
97
  string alwaysRestartFile;
98
+
99
+ /** Number of times a restart has been initiated so far. This is incremented immediately
100
+ * in Group::restart(), and is used to abort the restarter thread that was active at the
101
+ * time the restart was initiated. It's safe for the value to wrap around.
102
+ */
103
+ unsigned int restartsInitiated;
104
+ /**
105
+ * Whether process(es) are being spawned right now.
106
+ */
107
+ bool m_spawning;
108
+ /** Whether a non-rolling restart is in progress (i.e. whether spawnThreadRealMain()
109
+ * is at work). While it is in progress, it is not possible to signal the desire to
110
+ * spawn new process. If spawning was already in progress when the restart was initiated,
111
+ * then the spawning will abort as soon as possible.
112
+ *
113
+ * When rolling restarting is in progress, this flag is false.
114
+ *
115
+ * Invariant:
116
+ * if m_restarting: !m_spawning
117
+ */
118
+ bool m_restarting;
119
+ /**
120
+ * Do not access directly, always use `isAlive()`/`getLifeStatus()` or
121
+ * through `lifetimeSyncher`.
122
+ *
123
+ * Invariant:
124
+ * if lifeStatus != ALIVE:
125
+ * enabledCount == 0
126
+ * disablingCount == 0
127
+ * disabledCount == 0
128
+ */
129
+ enum LifeStatus {
130
+ /** Up and operational. */
131
+ ALIVE,
132
+ /** Being shut down. The containing SuperGroup has issued the shutdown()
133
+ * command, and this Group is now waiting for all detached processes to
134
+ * exit. You cannot call `get()`, `restart()` and other mutating methods
135
+ * anymore, and all threads created by this Group will exit as soon
136
+ * as possible.
137
+ */
138
+ SHUTTING_DOWN,
139
+ /**
140
+ * Shut down. Object no longer usable. No Processes are referenced from
141
+ * this Group anymore.
142
+ */
143
+ SHUT_DOWN
144
+ } lifeStatus;
145
+
146
+ /** Contains the spawn loop thread and the restarter thread. */
147
+ dynamic_thread_group interruptableThreads;
148
+
149
+ /** This timer scans `detachedProcesses` periodically to see
150
+ * whether any of the Processes can be shut down.
151
+ */
152
+ bool detachedProcessesCheckerActive;
153
+ condition_variable detachedProcessesCheckerCond;
154
+ Callback shutdownCallback;
155
+ GroupPtr selfPointer;
87
156
 
88
157
 
89
158
  static void _onSessionInitiateFailure(Session *session) {
90
- const ProcessPtr &process = session->getProcess();
91
- GroupPtr group = process->getGroup();
92
- if (OXT_LIKELY(group != NULL)) {
93
- group->onSessionInitiateFailure(process, session);
94
- }
159
+ ProcessPtr process = session->getProcess();
160
+ assert(process != NULL);
161
+ process->getGroup()->onSessionInitiateFailure(process, session);
95
162
  }
96
163
 
97
164
  static void _onSessionClose(Session *session) {
98
- const ProcessPtr &process = session->getProcess();
99
- GroupPtr group = process->getGroup();
100
- if (OXT_LIKELY(group != NULL)) {
101
- group->onSessionClose(process, session);
102
- }
165
+ ProcessPtr process = session->getProcess();
166
+ assert(process != NULL);
167
+ process->getGroup()->onSessionClose(process, session);
103
168
  }
104
169
 
105
- void createInterruptableThread(const function<void ()> &func, const string &name,
106
- unsigned int stackSize);
107
170
  static string generateSecret(const SuperGroupPtr &superGroup);
108
171
  void onSessionInitiateFailure(const ProcessPtr &process, Session *session);
109
172
  void onSessionClose(const ProcessPtr &process, Session *session);
110
173
  void lockAndAsyncOOBWRequestIfNeeded(const ProcessPtr &process, DisableResult result, GroupPtr self);
111
174
  void asyncOOBWRequestIfNeeded(const ProcessPtr &process);
112
175
  void spawnThreadOOBWRequest(GroupPtr self, ProcessPtr process);
113
- void spawnThreadMain(GroupPtr self, SpawnerPtr spawner, Options options);
114
- void spawnThreadRealMain(const SpawnerPtr &spawner, const Options &options);
176
+ void spawnThreadMain(GroupPtr self, SpawnerPtr spawner, Options options,
177
+ unsigned int restartsInitiated);
178
+ void spawnThreadRealMain(const SpawnerPtr &spawner, const Options &options,
179
+ unsigned int restartsInitiated);
115
180
  void finalizeRestart(GroupPtr self, Options options, SpawnerFactoryPtr spawnerFactory,
116
181
  vector<Callback> postLockActions);
182
+ void startCheckingDetachedProcesses(bool immediately);
183
+ void detachedProcessesCheckerMain(GroupPtr self);
117
184
  bool poolAtFullCapacity() const;
118
185
  bool anotherGroupIsWaitingForCapacity() const;
119
186
 
@@ -127,6 +194,8 @@ private:
127
194
  assert(!( enabledCount == 0 && disablingCount > 0 ) || spawning());
128
195
  assert(!( !spawning() ) || ( enabledCount > 0 || disablingCount == 0 ));
129
196
 
197
+ assert((lifeStatus == ALIVE) == (spawner != NULL));
198
+
130
199
  // Verify getWaitlist invariants.
131
200
  assert(!( !getWaitlist.empty() ) || ( enabledProcesses.empty() || pqueue.top()->atFullCapacity() ));
132
201
  assert(!( !enabledProcesses.empty() && !pqueue.top()->atFullCapacity() ) || ( getWaitlist.empty() ));
@@ -138,6 +207,12 @@ private:
138
207
 
139
208
  // Verify m_spawning and m_restarting.
140
209
  assert(!( m_restarting ) || !m_spawning);
210
+
211
+ // Verify lifeStatus.
212
+ LifeStatus lifeStatus = getLifeStatus();
213
+ assert(!( lifeStatus != ALIVE ) || ( enabledCount == 0 ));
214
+ assert(!( lifeStatus != ALIVE ) || ( disablingCount == 0 ));
215
+ assert(!( lifeStatus != ALIVE ) || ( disabledCount == 0 ));
141
216
  }
142
217
 
143
218
  void verifyExpensiveInvariants() const {
@@ -155,6 +230,7 @@ private:
155
230
  const ProcessPtr &process = *it;
156
231
  assert(process->enabled == Process::ENABLED);
157
232
  assert(process->pqHandle != NULL);
233
+ assert(process->isAlive());
158
234
  }
159
235
 
160
236
  end = disablingProcesses.end();
@@ -162,6 +238,7 @@ private:
162
238
  const ProcessPtr &process = *it;
163
239
  assert(process->enabled == Process::DISABLING);
164
240
  assert(process->pqHandle == NULL);
241
+ assert(process->isAlive());
165
242
  }
166
243
 
167
244
  end = disabledProcesses.end();
@@ -169,10 +246,19 @@ private:
169
246
  const ProcessPtr &process = *it;
170
247
  assert(process->enabled == Process::DISABLED);
171
248
  assert(process->pqHandle == NULL);
249
+ assert(process->isAlive());
250
+ }
251
+
252
+ foreach (const ProcessPtr &process, detachedProcesses) {
253
+ assert(process->getLifeStatus() == Process::SHUTTING_DOWN);
254
+ assert(process->pqHandle == NULL);
172
255
  }
173
256
  #endif
174
257
  }
175
258
 
259
+ /**
260
+ * Sets options for this Group. Called at creation time and at restart time.
261
+ */
176
262
  void resetOptions(const Options &newOptions) {
177
263
  options = newOptions;
178
264
  options.persist(newOptions);
@@ -180,6 +266,9 @@ private:
180
266
  options.groupSecret = secret;
181
267
  }
182
268
 
269
+ /**
270
+ * Merges some of the new options from the latest get() request into this Group.
271
+ */
183
272
  void mergeOptions(const Options &other) {
184
273
  options.maxRequests = other.maxRequests;
185
274
  options.minProcesses = other.minProcesses;
@@ -187,19 +276,15 @@ private:
187
276
  options.maxPreloaderIdleTime = other.maxPreloaderIdleTime;
188
277
  }
189
278
 
190
- void runAllActions(const vector<Callback> &actions) {
279
+ static void runAllActions(const vector<Callback> &actions) {
191
280
  vector<Callback>::const_iterator it, end = actions.end();
192
281
  for (it = actions.begin(); it != end; it++) {
193
282
  (*it)();
194
283
  }
195
284
  }
196
285
 
197
- static void cleanupSpawner(SpawnerPtr spawner) {
198
- try {
199
- spawner->cleanup();
200
- } catch (const thread_interrupted &) {
201
- // Return.
202
- }
286
+ static void doCleanupSpawner(SpawnerPtr spawner) {
287
+ spawner->cleanup();
203
288
  }
204
289
 
205
290
  SessionPtr newSession(Process *process = NULL) {
@@ -235,21 +320,26 @@ private:
235
320
  * This function does not fix getWaitlist invariants or other stuff.
236
321
  */
237
322
  void removeProcessFromList(const ProcessPtr &process, ProcessList &source) {
323
+ ProcessPtr p = process; // Keep an extra reference count just in case.
238
324
  source.erase(process->it);
239
- switch (process->enabled) {
240
- case Process::ENABLED:
241
- enabledCount--;
242
- pqueue.erase(process->pqHandle);
243
- process->pqHandle = NULL;
244
- break;
245
- case Process::DISABLING:
246
- disablingCount--;
247
- break;
248
- case Process::DISABLED:
249
- disabledCount--;
250
- break;
251
- default:
252
- abort();
325
+ if (process->isAlive()) {
326
+ switch (process->enabled) {
327
+ case Process::ENABLED:
328
+ enabledCount--;
329
+ pqueue.erase(process->pqHandle);
330
+ process->pqHandle = NULL;
331
+ break;
332
+ case Process::DISABLING:
333
+ disablingCount--;
334
+ break;
335
+ case Process::DISABLED:
336
+ disabledCount--;
337
+ break;
338
+ default:
339
+ P_BUG("Unknown 'enabled' state " << (int) process->enabled);
340
+ }
341
+ } else {
342
+ assert(&source == &detachedProcesses);
253
343
  }
254
344
  }
255
345
 
@@ -273,8 +363,10 @@ private:
273
363
  assert(process->sessions == 0);
274
364
  process->enabled = Process::DISABLED;
275
365
  disabledCount++;
366
+ } else if (&destination == &detachedProcesses) {
367
+ assert(process->isAlive());
276
368
  } else {
277
- abort();
369
+ P_BUG("Unknown destination list");
278
370
  }
279
371
  }
280
372
 
@@ -344,17 +436,6 @@ private:
344
436
  }
345
437
  }
346
438
  }
347
-
348
- void assignExceptionToGetWaiters(const ExceptionPtr &exception,
349
- vector<Callback> &postLockActions)
350
- {
351
- while (!getWaitlist.empty()) {
352
- postLockActions.push_back(boost::bind(
353
- getWaitlist.front().callback, SessionPtr(),
354
- exception));
355
- getWaitlist.pop();
356
- }
357
- }
358
439
 
359
440
  void enableAllDisablingProcesses(vector<Callback> &postLockActions) {
360
441
  deque<DisableWaiter>::iterator it, end = disableWaitlist.end();
@@ -400,6 +481,46 @@ private:
400
481
  disableWaitlist.pop_front();
401
482
  }
402
483
  }
484
+
485
+ void shutdownAndRemoveProcess(const ProcessPtr &process) {
486
+ TRACE_POINT();
487
+ const ProcessPtr p = process;
488
+ assert(process->canBeShutDown());
489
+ removeProcessFromList(process, detachedProcesses);
490
+ process->shutdown();
491
+ }
492
+
493
+ bool shutdownCanFinish() const {
494
+ return getLifeStatus() == SHUTTING_DOWN
495
+ && enabledCount == 0
496
+ && disablingCount == 0
497
+ && disabledCount == 0
498
+ && detachedProcesses.empty();
499
+ }
500
+
501
+ static void interruptAndJoinAllThreads(GroupPtr self) {
502
+ self->interruptableThreads.interrupt_and_join_all();
503
+ }
504
+
505
+ /** One of the post lock actions can potentially perform a long-running
506
+ * operation, so running them in a thread is advised.
507
+ */
508
+ void finishShutdown(vector<Callback> &postLockActions) {
509
+ TRACE_POINT();
510
+ assert(getLifeStatus() == SHUTTING_DOWN);
511
+ P_DEBUG("Finishing shutdown of group " << name);
512
+ if (shutdownCallback) {
513
+ postLockActions.push_back(shutdownCallback);
514
+ shutdownCallback = Callback();
515
+ }
516
+ postLockActions.push_back(boost::bind(interruptAndJoinAllThreads,
517
+ shared_from_this()));
518
+ {
519
+ lock_guard<boost::mutex> l(lifetimeSyncher);
520
+ lifeStatus = SHUT_DOWN;
521
+ }
522
+ selfPointer.reset();
523
+ }
403
524
 
404
525
  public:
405
526
  Options options;
@@ -456,12 +577,15 @@ public:
456
577
  * for all process in enabledProcesses:
457
578
  * process.enabled == Process::ENABLED
458
579
  * process.pqHandle != NULL
580
+ * process.isAlive()
459
581
  * for all processes in disablingProcesses:
460
582
  * process.enabled == Process::DISABLING
461
583
  * process.pqHandle == NULL
584
+ * process.isAlive()
462
585
  * for all process in disabledProcesses:
463
586
  * process.enabled == Process::DISABLED
464
587
  * process.pqHandle == NULL
588
+ * process.isAlive()
465
589
  */
466
590
  int enabledCount;
467
591
  int disablingCount;
@@ -470,11 +594,25 @@ public:
470
594
  ProcessList enabledProcesses;
471
595
  ProcessList disablingProcesses;
472
596
  ProcessList disabledProcesses;
597
+
598
+ /**
599
+ * When a process is detached, it is stored here until we've confirmed
600
+ * that it can be shut down.
601
+ *
602
+ * for all process in detachedProcesses:
603
+ * process.getLifeStatus() == Process::SHUTTING_DOWN
604
+ * process.pqHandle == NULL
605
+ */
606
+ ProcessList detachedProcesses;
473
607
 
474
608
  /**
475
609
  * get() requests for this group that cannot be immediately satisfied are
476
610
  * put on this wait list, which must be processed as soon as the necessary
477
611
  * resources have become free.
612
+ *
613
+ * 'std::' is required because Solaris in its infinite wisdom has a C
614
+ * struct in its system headers called 'queue'.
615
+ * http://code.google.com/p/phusion-passenger/issues/detail?id=840
478
616
  *
479
617
  * Invariant 1:
480
618
  * if getWaitlist is non-empty:
@@ -490,7 +628,7 @@ public:
490
628
  * if getWaitlist is non-empty:
491
629
  * !enabledProcesses.empty() || spawning() || restarting() || poolAtFullCapacity()
492
630
  */
493
- queue<GetWaiter> getWaitlist;
631
+ std::queue<GetWaiter> getWaitlist;
494
632
  /**
495
633
  * Disable() commands that couldn't finish immediately will put their callbacks
496
634
  * in this queue. Note that there may be multiple DisableWaiters pointing to the
@@ -501,26 +639,50 @@ public:
501
639
  */
502
640
  deque<DisableWaiter> disableWaitlist;
503
641
 
504
- SpawnerPtr spawner;
505
642
  /**
506
- * Whether process(es) are being spawned right now.
507
- */
508
- bool m_spawning;
509
- /** Whether a non-rolling restart is in progress. While it is in progress,
510
- * it is not possible to signal the desire to spawn new process. If spawning
511
- * was already in progress when the restart was initiated, then the spawning
512
- * will abort as soon as possible.
513
- *
514
- * When rolling restarting is in progress, this flag is false.
515
- *
516
643
  * Invariant:
517
- * if m_restarting: !m_spawning
644
+ * (lifeStatus == ALIVE) == (spawner != NULL)
518
645
  */
519
- bool m_restarting;
646
+ SpawnerPtr spawner;
520
647
 
521
648
  Group(const SuperGroupPtr &superGroup, const Options &options, const ComponentInfo &info);
522
-
649
+ ~Group();
650
+
651
+ /**
652
+ * Must be called before destroying a Group. You can optionally provide a
653
+ * callback so that you are notified when shutdown has finished.
654
+ *
655
+ * The caller is responsible for migrating waiters on the getWaitlist.
656
+ *
657
+ * One of the post lock actions can potentially perform a long-running
658
+ * operation, so running them in a thread is advised.
659
+ */
660
+ void shutdown(const Callback &callback, vector<Callback> &postLockActions) {
661
+ assert(isAlive());
662
+
663
+ P_DEBUG("Shutting down group " << name);
664
+ shutdownCallback = callback;
665
+ detachAll(postLockActions);
666
+ startCheckingDetachedProcesses(true);
667
+ interruptableThreads.interrupt_all();
668
+ postLockActions.push_back(boost::bind(doCleanupSpawner, spawner));
669
+ spawner.reset();
670
+ selfPointer = shared_from_this();
671
+ assert(disableWaitlist.empty());
672
+ {
673
+ lock_guard<boost::mutex> l(lifetimeSyncher);
674
+ lifeStatus = SHUTTING_DOWN;
675
+ }
676
+ if (shutdownCanFinish()) {
677
+ finishShutdown(postLockActions);
678
+ } else {
679
+ P_DEBUG("Shutdown finalization of group " << name << " has been deferred");
680
+ }
681
+ }
682
+
523
683
  SessionPtr get(const Options &newOptions, const GetCallback &callback) {
684
+ assert(isAlive());
685
+
524
686
  if (OXT_LIKELY(!restarting())) {
525
687
  if (OXT_UNLIKELY(needsRestart(newOptions))) {
526
688
  restart(newOptions);
@@ -537,6 +699,8 @@ public:
537
699
  0, string(), string(),
538
700
  FileDescriptor(), FileDescriptor(),
539
701
  SocketListPtr(), 0, 0);
702
+ process->dummy = true;
703
+ process->requiresShutdown = false;
540
704
  process->setGroup(shared_from_this());
541
705
  return make_shared<Session>(process, (Socket *) NULL);
542
706
  }
@@ -586,27 +750,40 @@ public:
586
750
  }
587
751
  }
588
752
 
589
- // Thread-safe.
753
+ /**
754
+ * Thread-safe.
755
+ * @pre getLifeState() != SHUT_DOWN
756
+ * @post result != NULL
757
+ */
590
758
  SuperGroupPtr getSuperGroup() const {
591
- lock_guard<boost::mutex> lock(backrefSyncher);
592
759
  return superGroup.lock();
593
760
  }
594
761
 
595
- // Thread-safe.
596
762
  void setSuperGroup(const SuperGroupPtr &superGroup) {
597
- lock_guard<boost::mutex> lock(backrefSyncher);
763
+ assert(this->superGroup.lock() == NULL);
598
764
  this->superGroup = superGroup;
599
765
  }
600
766
 
601
- // Thread-safe.
767
+ /**
768
+ * Thread-safe.
769
+ * @pre getLifeState() != SHUT_DOWN
770
+ * @post result != NULL
771
+ */
602
772
  PoolPtr getPool() const;
603
773
 
604
774
  // Thread-safe.
605
- bool detached() const {
606
- return getSuperGroup() == NULL;
775
+ bool isAlive() const {
776
+ lock_guard<boost::mutex> lock(lifetimeSyncher);
777
+ return lifeStatus == ALIVE;
607
778
  }
608
-
779
+
609
780
  // Thread-safe.
781
+ LifeStatus getLifeStatus() const {
782
+ lock_guard<boost::mutex> lock(lifetimeSyncher);
783
+ return lifeStatus;
784
+ }
785
+
786
+ // Thread-safe, but only call outside the pool lock!
610
787
  void requestOOBW(const ProcessPtr &process);
611
788
 
612
789
  /**
@@ -615,7 +792,10 @@ public:
615
792
  * afterwards if necessary.
616
793
  */
617
794
  void attach(const ProcessPtr &process, vector<Callback> &postLockActions) {
618
- assert(process->getGroup() == NULL);
795
+ assert(process->getGroup() == NULL || process->getGroup().get() == this);
796
+ assert(process->isAlive());
797
+ assert(isAlive());
798
+
619
799
  process->setGroup(shared_from_this());
620
800
  P_DEBUG("Attaching process " << process->inspect());
621
801
  addProcessToList(process, enabledProcesses);
@@ -657,10 +837,11 @@ public:
657
837
  */
658
838
  void detach(const ProcessPtr &process, vector<Callback> &postLockActions) {
659
839
  assert(process->getGroup().get() == this);
840
+ assert(process->isAlive());
841
+ assert(isAlive());
660
842
 
661
843
  const ProcessPtr p = process; // Keep an extra reference just in case.
662
844
  P_DEBUG("Detaching process " << process->inspect());
663
- process->setGroup(GroupPtr());
664
845
 
665
846
  if (process->enabled == Process::ENABLED || process->enabled == Process::DISABLING) {
666
847
  assert(enabledCount > 0 || disablingCount > 0);
@@ -674,6 +855,14 @@ public:
674
855
  assert(!disabledProcesses.empty());
675
856
  removeProcessFromList(process, disabledProcesses);
676
857
  }
858
+
859
+ addProcessToList(process, detachedProcesses);
860
+ process->setShuttingDown();
861
+ if (process->canBeShutDown()) {
862
+ shutdownAndRemoveProcess(process);
863
+ } else {
864
+ startCheckingDetachedProcesses(false);
865
+ }
677
866
  }
678
867
 
679
868
  /**
@@ -681,17 +870,21 @@ public:
681
870
  * getWaitlist so be sure to fix its invariants afterwards if necessary.
682
871
  */
683
872
  void detachAll(vector<Callback> &postLockActions) {
684
- ProcessList::iterator it, end = enabledProcesses.end();
685
- for (it = enabledProcesses.begin(); it != end; it++) {
686
- (*it)->setGroup(GroupPtr());
873
+ assert(isAlive());
874
+ P_DEBUG("Detaching all processes in group " << name);
875
+
876
+ foreach (ProcessPtr process, enabledProcesses) {
877
+ addProcessToList(process, detachedProcesses);
878
+ process->pqHandle = NULL;
879
+ process->setShuttingDown();
687
880
  }
688
- end = disablingProcesses.end();
689
- for (it = disablingProcesses.begin(); it != end; it++) {
690
- (*it)->setGroup(GroupPtr());
881
+ foreach (ProcessPtr process, disablingProcesses) {
882
+ addProcessToList(process, detachedProcesses);
883
+ process->setShuttingDown();
691
884
  }
692
- end = disabledProcesses.end();
693
- for (it = disabledProcesses.begin(); it != end; it++) {
694
- (*it)->setGroup(GroupPtr());
885
+ foreach (ProcessPtr process, disabledProcesses) {
886
+ addProcessToList(process, detachedProcesses);
887
+ process->setShuttingDown();
695
888
  }
696
889
 
697
890
  enabledProcesses.clear();
@@ -702,6 +895,7 @@ public:
702
895
  disablingCount = 0;
703
896
  disabledCount = 0;
704
897
  clearDisableWaitlist(DR_NOOP, postLockActions);
898
+ startCheckingDetachedProcesses(false);
705
899
  }
706
900
 
707
901
  /**
@@ -710,6 +904,9 @@ public:
710
904
  */
711
905
  void enable(const ProcessPtr &process, vector<Callback> &postLockActions) {
712
906
  assert(process->getGroup().get() == this);
907
+ assert(process->isAlive());
908
+ assert(isAlive());
909
+
713
910
  if (process->enabled == Process::DISABLING) {
714
911
  P_DEBUG("Enabling DISABLING process " << process->inspect());
715
912
  removeProcessFromList(process, disablingProcesses);
@@ -731,6 +928,9 @@ public:
731
928
  */
732
929
  DisableResult disable(const ProcessPtr &process, const DisableCallback &callback) {
733
930
  assert(process->getGroup().get() == this);
931
+ assert(process->isAlive());
932
+ assert(isAlive());
933
+
734
934
  if (process->enabled == Process::ENABLED) {
735
935
  P_DEBUG("Disabling ENABLED process " << process->inspect() <<
736
936
  "; enabledCount=" << enabledCount << ", process.sessions=" << process->sessions);
@@ -771,11 +971,9 @@ public:
771
971
  }
772
972
  }
773
973
 
774
- void asyncCleanupSpawner() {
775
- createInterruptableThread(
776
- boost::bind(cleanupSpawner, spawner),
777
- "Group spawner cleanup: " + name,
778
- POOL_HELPER_THREAD_STACK_SIZE);
974
+ void cleanupSpawner(vector<Callback> &postLockActions) {
975
+ assert(isAlive());
976
+ postLockActions.push_back(boost::bind(doCleanupSpawner, spawner));
779
977
  }
780
978
 
781
979
  unsigned int utilization() const {
@@ -799,8 +997,7 @@ public:
799
997
  return false;
800
998
  }
801
999
 
802
- /** Whether a new process should be spawned for this group.
803
- */
1000
+ /** Whether a new process should be spawned for this group. */
804
1001
  bool shouldSpawn() const;
805
1002
  /** Whether a new process should be spawned for this group in the
806
1003
  * specific case that another get action is to be performed.
@@ -812,12 +1009,14 @@ public:
812
1009
  * Will ensure that at least options.minProcesses processes are spawned.
813
1010
  */
814
1011
  void spawn() {
1012
+ assert(isAlive());
815
1013
  if (!spawning() && !restarting()) {
816
1014
  P_DEBUG("Requested spawning of new process for group " << name);
817
- createInterruptableThread(
1015
+ interruptableThreads.create_thread(
818
1016
  boost::bind(&Group::spawnThreadMain,
819
1017
  this, shared_from_this(), spawner,
820
- options.copyAndPersist().clearPerRequestFields()),
1018
+ options.copyAndPersist().clearPerRequestFields(),
1019
+ restartsInitiated),
821
1020
  "Group process spawner: " + name,
822
1021
  POOL_HELPER_THREAD_STACK_SIZE);
823
1022
  m_spawning = true;
@@ -848,7 +1047,7 @@ public:
848
1047
  * Checks whether this group is waiting for capacity on the pool to
849
1048
  * become available before it can continue processing requests.
850
1049
  */
851
- bool isWaitingForCapacity() {
1050
+ bool isWaitingForCapacity() const {
852
1051
  return enabledProcesses.empty()
853
1052
  && !m_spawning
854
1053
  && !m_restarting
@@ -879,6 +1078,19 @@ public:
879
1078
  if (includeSecrets) {
880
1079
  stream << "<secret>" << escapeForXml(secret) << "</secret>";
881
1080
  }
1081
+ switch (lifeStatus) {
1082
+ case ALIVE:
1083
+ stream << "<life_status>alive</life_status>";
1084
+ break;
1085
+ case SHUTTING_DOWN:
1086
+ stream << "<life_status>shutting_down</life_status>";
1087
+ break;
1088
+ case SHUT_DOWN:
1089
+ stream << "<life_status>shut_down</life_status>";
1090
+ break;
1091
+ default:
1092
+ P_BUG("Unknown 'lifeStatus' state " << (int) lifeStatus);
1093
+ }
882
1094
 
883
1095
  stream << "<processes>";
884
1096
 
@@ -897,6 +1109,11 @@ public:
897
1109
  (*it)->inspectXml(stream, includeSecrets);
898
1110
  stream << "</process>";
899
1111
  }
1112
+ for (it = detachedProcesses.begin(); it != detachedProcesses.end(); it++) {
1113
+ stream << "<process>";
1114
+ (*it)->inspectXml(stream, includeSecrets);
1115
+ stream << "</process>";
1116
+ }
900
1117
 
901
1118
  stream << "</processes>";
902
1119
  }