passenger 5.0.0.rc2 → 5.0.1

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 (36) hide show
  1. checksums.yaml +8 -8
  2. checksums.yaml.gz.asc +7 -7
  3. data.tar.gz.asc +7 -7
  4. data/CHANGELOG +10 -0
  5. data/CONTRIBUTORS +1 -0
  6. data/build/agents.rb +6 -0
  7. data/build/cxx_tests.rb +6 -0
  8. data/build/misc.rb +22 -1
  9. data/doc/Users guide Nginx.txt +12 -0
  10. data/ext/common/ApplicationPool2/Options.h +2 -1
  11. data/ext/common/ApplicationPool2/Pool.h +15 -744
  12. data/ext/common/ApplicationPool2/Pool/AnalyticsCollection.h +255 -0
  13. data/ext/common/ApplicationPool2/Pool/Debug.h +63 -0
  14. data/ext/common/ApplicationPool2/Pool/GarbageCollection.h +197 -0
  15. data/ext/common/ApplicationPool2/Pool/GeneralUtils.h +127 -0
  16. data/ext/common/ApplicationPool2/Pool/Inspection.h +214 -0
  17. data/ext/common/ApplicationPool2/Pool/ProcessUtils.h +85 -0
  18. data/ext/common/ApplicationPool2/Process.h +5 -7
  19. data/ext/common/Constants.h +1 -1
  20. data/ext/common/Hooks.h +2 -1
  21. data/ext/common/Utils.cpp +1 -1
  22. data/ext/common/Utils/JsonUtils.h +37 -1
  23. data/ext/common/agents/Base.cpp +45 -40
  24. data/ext/common/agents/Base.h +3 -2
  25. data/ext/common/agents/HelperAgent/RequestHandler/InitRequest.cpp +12 -10
  26. data/ext/nginx/Configuration.c +4 -1
  27. data/lib/phusion_passenger.rb +1 -1
  28. data/lib/phusion_passenger/common_library.rb +7 -1
  29. data/lib/phusion_passenger/config/restart_app_command.rb +40 -2
  30. data/lib/phusion_passenger/config/utils.rb +3 -5
  31. data/lib/phusion_passenger/utils/ansi_colors.rb +28 -21
  32. data/lib/phusion_passenger/utils/terminal_choice_menu.rb +27 -10
  33. data/resources/templates/standalone/config.erb +4 -4
  34. data/test/cxx/CxxTestMain.cpp +10 -22
  35. metadata +10 -4
  36. metadata.gz.asc +7 -7
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YzkyZGI4ZTVlOGU4MzAwYjYxNzcxMWUwM2I0NGM0MDVjOTM3ZWVmYw==
4
+ MzQxOGI3N2E5ZWJiMjU3MGY2YmI4NzAxYWQ1ZTdlYTA3Yjk5N2FiMQ==
5
5
  data.tar.gz: !binary |-
6
- ZmQ2ODI5ZTE3NzczZGRlMjE1YTQ5NTdlN2ZkOTQxYTEyNTUzODA1OA==
6
+ ZjYxYTNjYjU0M2IxZWVjNzFmN2VmZGM2NWRkODg2NjliMTRiODE5Nw==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- MWY3MzEzY2UzNDAyOTEwYjg1ZWU1YmI2ZDM2OWNmN2JmMGExMjRjMDMyNjBk
10
- NWQxNWRhOTZiMWFhZTE3MjU0MmNiZDZjNWQ3YTJkMzcyYzNmMmFkZTgwMzlk
11
- MzE2Mzk0NTA3ZTQ1NGVkZjViNWMyZDYwNGM0YWZhMGMwNjY5MmY=
9
+ NWNmNjM1MWEzYWU3ZjA3MjM3NmI2ODNlOWQ1MDcxYzUwMTU2NWEyZjJjZjc0
10
+ NDlhMDJjMTdlOWJlYjcxYmI5OGU0ODczOWU2Y2NjZjQzOWMwM2I3YmY0Y2Rm
11
+ NzZhNWY0MzY0M2VhMzRhMDQ0MjExNzdlYTM2OGU3YzdmNmU0NDU=
12
12
  data.tar.gz: !binary |-
13
- YWZjN2JkMTYwZjAxZDM3NmZkMDExZjM1YTMwODUyZDc1ZjllZDE4N2JmOWYz
14
- NTdiNTcyNWNmYjQwMzYwMTdlOWQ3N2ZkM2RhNTk3ZTIzNTlmYTE4NWUyZTVj
15
- MThjNDQzMzNmZjY3MGNlOTRiNzEzOWVmZGNkZjcyYzBhYmEyMDY=
13
+ ZTg5MGY4MTZjYTdkNmMzYjFlNDNhM2U3M2U1ODc1M2QxY2I2NDI1MTBlYzIz
14
+ NGU4MjVkN2JjZmNlMzg0MDNjNzhiOWZkNGRlMDMxMGZlYzc2NDVlMzkwN2Iy
15
+ MTBkYTA2OWFhOTM4MWU5MmUyZDRhMDE4MmJlN2U4NmNmNDc3MzM=
checksums.yaml.gz.asc CHANGED
@@ -2,11 +2,11 @@
2
2
  Version: GnuPG/MacGPG2 v2.0.17 (Darwin)
3
3
  Comment: GPGTools - http://gpgtools.org
4
4
 
5
- iQEcBAABAgAGBQJU5zPQAAoJECrHRaUKISqM5EAH/14wc1xeU1EglMA+EXM2mows
6
- 2i17hxn7GzsRiSOeo+F1Xm5fKqQ7NauefjH3Zrz6WAyaJyakwhkkuJYw6z+3Fp1i
7
- pqrT4puKOQJ3G/2j1hkHbmiRyXLgWUu5Z5gnf45689Nhq80vjZW8X4bBaqeh6ZuR
8
- 67Ch4c8z3JeeSYz05A+S8Evg3RY9SA93lsar+BdTEopGrNhfx4p0DHlb2YwFuMkh
9
- wJCHG0K2AFleOmrWWaUH9Wg7s8rT4C6Lxy8Hg4Xmda3FF71iahy/19D4EmuuC1+s
10
- L8wupN2tNq0OwqGwoNFb44QTiistw7C15TSbd0wQQ2Yp7O+ZyK5r+lI2WWY8gaQ=
11
- =SoHV
5
+ iQEcBAABAgAGBQJU9xtNAAoJECrHRaUKISqMJQAIAJrl0J6b4TJ5HELJjdVBV3Qq
6
+ nZXFQt1JW6nf2i2/vAabc1LPG4RcMakOuzKmn3HLFuxasaZXHhrNtAMLboaIAs7n
7
+ Hk2PuOh01nvbLm/mSVajzfRL9EkUfjJpoFtLfwlRt5c7J++PmbdFdNucsAYzIYgl
8
+ 12nRnu6CUc7SxVki6QC5mZwJeKcR/QvzCWG7bJ2NDvvQWoKcmkksHlBmSOhPQWC9
9
+ lExE5ZgZSjmjMeBd1peheNLPW62SVlZBKOgA32yA5wNg/KqZ88YalxQp82Pit44N
10
+ 9iCA7A3PW+dUcrjbmXRYKDYSOg0SIAJvjCdRhHdcssbQPlGVbFrEZfRHrWxaRVw=
11
+ =xWOs
12
12
  -----END PGP SIGNATURE-----
data.tar.gz.asc CHANGED
@@ -2,11 +2,11 @@
2
2
  Version: GnuPG/MacGPG2 v2.0.17 (Darwin)
3
3
  Comment: GPGTools - http://gpgtools.org
4
4
 
5
- iQEcBAABAgAGBQJU5zPQAAoJECrHRaUKISqMfIMH/ibyDpj/kSqVKpaMJx9B1UBV
6
- tlfqXzlVT9rDLbsB6B8yFy2kXKKirRUkvFQmXw3l0xivi1xlyBcgW7PA9fkXeSvK
7
- oUfMkBKaZYKwpcBwC0Pnd3aKAZiD9sEfSrhR73xZSTKZQTJYomPSLmWLJikq/zX/
8
- iidzjpdQ2OkVxY8LdQj9FH+I2Ebazf1rKiSaY0OSNE5M4kYlm425QfBnfHvjp7Vr
9
- sv1zlxZtkAiMNsp0khBLb2203xYyy9X8DyhqChO8GuAbA9jfEmiveUNokx+oFCdO
10
- LOojfJN3f9LhcL2igWy99KmseREjty4q5qli0TFCxCCI4oKka8eUJn9lwX0PHWE=
11
- =O6BO
5
+ iQEcBAABAgAGBQJU9xtNAAoJECrHRaUKISqMRNsIAJ3SdZ3briMF68aVBwnDb9Ht
6
+ z4uRqGROnKA/FX7EpVRtxEypVaArGgvfSI+C2jho91GIBWoEL5S5YEbRL3RwUci0
7
+ Pr4Ts+g9OYvmzYFrTmzM75W8pF2nht1hm9bxu1xxdfQMo9ts8+J9vpFjPv31+d6z
8
+ o1JnVIHZXUw9NPeAUZzBN0DMozpDqY7JH1DXYzwR7uejikz3bDbTmnPq5xIv1RAO
9
+ 7aR1VfOrHm3HCs8HMPXO46vfdg4bnhrzhVQgj8xKiMXQJrd6Si/Tx6kDURDGt0zr
10
+ BBxiuYcCeVVG5LDCY6PCvaUNMal7m/98xG4AFCYpe3LirpthzAo6OZTDbaI8Dbk=
11
+ =1Ov4
12
12
  -----END PGP SIGNATURE-----
data/CHANGELOG CHANGED
@@ -1,3 +1,13 @@
1
+ Release 5.0.1
2
+ -------------
3
+
4
+ * The `passenger-config restart-app` command is now more user friendly. When run in a terminal, it will show an interactive menu, allowing you to select the app to restart. Closes GH-1387.
5
+ * Fixed a crash bug in the handling of sticky session cookies.
6
+ * Log failed program in error message, not its command line (contributed by: paisleyrob). Closes GH-1397.
7
+ * [Nginx] Fixes cases in which Passenger overrides the Nginx handler function even when it shouldn't, for example when Passenger is disabled. Closes GH-1393.
8
+ * [Enterprise] The `sticky_sessions` and `envvars` options in Passengerfile.json is now also supported in mass deployment mode.
9
+
10
+
1
11
  Release 5.0.0 release candidate 2
2
12
  ---------------------------------
3
13
 
data/CONTRIBUTORS CHANGED
@@ -67,6 +67,7 @@ Perry Smith
67
67
  Philip M. Gollucci
68
68
  Redmar Kerkhoff
69
69
  remi
70
+ Rob Paisley
70
71
  Robin Bowes
71
72
  Ryan Schwartz
72
73
  Ryo Onodera
data/build/agents.rb CHANGED
@@ -63,6 +63,12 @@ AGENT_OBJECTS = {
63
63
  'ext/common/ServerKit/AcceptLoadBalancer.h',
64
64
  'ext/common/ServerKit/FileBufferedChannel.h',
65
65
  'ext/common/ApplicationPool2/Pool.h',
66
+ 'ext/common/ApplicationPool2/Pool/AnalyticsCollection.h',
67
+ 'ext/common/ApplicationPool2/Pool/GarbageCollection.h',
68
+ 'ext/common/ApplicationPool2/Pool/GeneralUtils.h',
69
+ 'ext/common/ApplicationPool2/Pool/ProcessUtils.h',
70
+ 'ext/common/ApplicationPool2/Pool/Inspection.h',
71
+ 'ext/common/ApplicationPool2/Pool/Debug.h',
66
72
  'ext/common/ApplicationPool2/Group.h',
67
73
  'ext/common/ApplicationPool2/Spawner.h',
68
74
  'ext/common/Constants.h',
data/build/cxx_tests.rb CHANGED
@@ -77,6 +77,12 @@ TEST_CXX_OBJECTS = {
77
77
  ext/common/ApplicationPool2/SuperGroup.h
78
78
  ext/common/ApplicationPool2/Group.h
79
79
  ext/common/ApplicationPool2/Pool.h
80
+ ext/common/ApplicationPool2/Pool/AnalyticsCollection.h
81
+ ext/common/ApplicationPool2/Pool/GarbageCollection.h
82
+ ext/common/ApplicationPool2/Pool/GeneralUtils.h
83
+ ext/common/ApplicationPool2/Pool/ProcessUtils.h
84
+ ext/common/ApplicationPool2/Pool/Inspection.h
85
+ ext/common/ApplicationPool2/Pool/Debug.h
80
86
  ext/common/ApplicationPool2/Process.h
81
87
  ext/common/ApplicationPool2/Socket.h
82
88
  ext/common/ApplicationPool2/Options.h
data/build/misc.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
  # Phusion Passenger - https://www.phusionpassenger.com/
3
- # Copyright (c) 2010-2013 Phusion
3
+ # Copyright (c) 2010-2015 Phusion
4
4
  #
5
5
  # "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
6
6
  #
@@ -141,12 +141,33 @@ task :contributors do
141
141
  entries.push "Gokulnath Manakkattil"
142
142
  entries.push "Sean Wilkinson"
143
143
  entries.push "Yichun Zhang"
144
+ entries.delete "OnixGH"
144
145
  File.open("CONTRIBUTORS", "w") do |f|
145
146
  f.puts(entries.sort{ |a, b| a.downcase <=> b.downcase }.join("\n"))
146
147
  end
147
148
  puts "Updated CONTRIBUTORS"
148
149
  end
149
150
 
151
+ # Compile the WebHelper binary, used by Homebrew packaging.
152
+ task :webhelper => :nginx do
153
+ require 'tmpdir'
154
+ require 'logger'
155
+ PhusionPassenger.require_passenger_lib 'utils/download'
156
+ Dir.mktmpdir do |path|
157
+ Utils::Download.download("http://nginx.org/download/nginx-#{PREFERRED_NGINX_VERSION}.tar.gz",
158
+ "#{path}/nginx.tar.gz",
159
+ :connect_timeout => 30,
160
+ :idle_timeout => 30)
161
+ sh "cd '#{path}' && tar xzf nginx.tar.gz"
162
+ sh "cd '#{path}/nginx-#{PREFERRED_NGINX_VERSION}' && " +
163
+ "./configure --prefix=/tmp " +
164
+ "#{STANDALONE_NGINX_CONFIGURE_OPTIONS} " +
165
+ "--add-module='#{Dir.pwd}/ext/nginx' && " +
166
+ "make"
167
+ sh "cp '#{path}/nginx-#{PREFERRED_NGINX_VERSION}/objs/nginx' '#{AGENT_OUTPUT_DIR}nginx-#{PREFERRED_NGINX_VERSION}'"
168
+ end
169
+ end
170
+
150
171
  dependencies = [
151
172
  COMMON_LIBRARY.link_objects,
152
173
  LIBBOOST_OXT,
@@ -430,6 +430,18 @@ Please see <<deploying_a_rack_app,Deploying a Rack-based Ruby application>>
430
430
  and <<deploying_a_wsgi_app,Deploying a WSGI (Python) application>>
431
431
  for examples.
432
432
 
433
+ ------------------------------
434
+ server {
435
+ listen 80;
436
+ server_name www.example.com;
437
+ root /webapps/example/public;
438
+
439
+ # You must explicitly set 'passenger_enabled on', otherwise
440
+ # Phusion Passenger won't serve this app.
441
+ passenger_enabled on;
442
+ }
443
+ ------------------------------
444
+
433
445
  [[PassengerBaseURI]]
434
446
  ==== passenger_base_uri <uri>
435
447
  Used to specify that the given URI is an distinct application that should
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * Phusion Passenger - https://www.phusionpassenger.com/
3
- * Copyright (c) 2010-2014 Phusion
3
+ * Copyright (c) 2010-2015 Phusion
4
4
  *
5
5
  * "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
6
6
  *
@@ -33,6 +33,7 @@
33
33
  #include <Account.h>
34
34
  #include <UnionStation/Core.h>
35
35
  #include <UnionStation/Transaction.h>
36
+ #include <DataStructures/HashedStaticString.h>
36
37
  #include <Constants.h>
37
38
  #include <ResourceLocator.h>
38
39
  #include <StaticString.h>
@@ -76,70 +76,20 @@ class Pool: public boost::enable_shared_from_this<Pool> {
76
76
  public:
77
77
  typedef void (*AbortLongRunningConnectionsCallback)(const ProcessPtr &process);
78
78
 
79
- struct InspectOptions {
80
- bool colorize;
81
- bool verbose;
82
-
83
- InspectOptions()
84
- : colorize(false),
85
- verbose(false)
86
- { }
87
-
88
- InspectOptions(const VariantMap &options)
89
- : colorize(options.getBool("colorize", false, false)),
90
- verbose(options.getBool("verbose", false, false))
91
- { }
92
- };
93
-
94
79
  // Actually private, but marked public so that unit tests can access the fields.
95
80
  public:
96
81
  friend class SuperGroup;
97
82
  friend class Group;
98
-
99
- struct DebugSupport {
100
- /** Mailbox for the unit tests to receive messages on. */
101
- MessageBoxPtr debugger;
102
- /** Mailbox for the ApplicationPool code to receive messages on. */
103
- MessageBoxPtr messages;
104
-
105
- // Choose aspects to debug.
106
- bool restarting;
107
- bool spawning;
108
- bool superGroup;
109
- bool oobw;
110
- bool testOverflowRequestQueue;
111
- bool detachedProcessesChecker;
112
-
113
- // The following fields may only be accessed by Pool.
114
- boost::mutex syncher;
115
- unsigned int spawnLoopIteration;
116
-
117
- DebugSupport() {
118
- debugger = boost::make_shared<MessageBox>();
119
- messages = boost::make_shared<MessageBox>();
120
- restarting = true;
121
- spawning = true;
122
- superGroup = false;
123
- oobw = false;
124
- detachedProcessesChecker = false;
125
- testOverflowRequestQueue = false;
126
- spawnLoopIteration = 0;
127
- }
128
- };
129
-
130
- typedef boost::shared_ptr<DebugSupport> DebugSupportPtr;
83
+ friend class Process;
84
+ friend struct tut::ApplicationPool2_PoolTest;
131
85
 
132
86
  SpawnerFactoryPtr spawnerFactory;
133
- SystemMetricsCollector systemMetricsCollector;
134
- SystemMetrics systemMetrics;
135
87
 
136
88
  mutable boost::mutex syncher;
137
89
  unsigned int max;
138
90
  unsigned long long maxIdleTime;
139
91
  bool selfchecking;
140
92
 
141
- boost::condition_variable garbageCollectionCond;
142
-
143
93
  /**
144
94
  * Code can register background threads in one of these dynamic thread groups
145
95
  * to ensure that threads are interrupted and/or joined properly upon Pool
@@ -203,168 +153,16 @@ public:
203
153
  vector<GetWaiter> getWaitlist;
204
154
 
205
155
  const VariantMap *agentsOptions;
206
- DebugSupportPtr debugSupport;
207
-
208
- static void runAllActions(const boost::container::vector<Callback> &actions) {
209
- boost::container::vector<Callback>::const_iterator it, end = actions.end();
210
- for (it = actions.begin(); it != end; it++) {
211
- (*it)();
212
- }
213
- }
214
-
215
- static void runAllActionsWithCopy(boost::container::vector<Callback> actions) {
216
- runAllActions(actions);
217
- }
218
-
219
- static const char *maybeColorize(const InspectOptions &options, const char *color) {
220
- if (options.colorize) {
221
- return color;
222
- } else {
223
- return "";
224
- }
225
- }
226
-
227
- void verifyInvariants() const {
228
- // !a || b: logical equivalent of a IMPLIES b.
229
- #ifndef NDEBUG
230
- if (!selfchecking) {
231
- return;
232
- }
233
- assert(!( !getWaitlist.empty() ) || ( atFullCapacityUnlocked() ));
234
- assert(!( !atFullCapacityUnlocked() ) || ( getWaitlist.empty() ));
235
- #endif
236
- }
237
-
238
- void verifyExpensiveInvariants() const {
239
- #ifndef NDEBUG
240
- if (!selfchecking) {
241
- return;
242
- }
243
- vector<GetWaiter>::const_iterator it, end = getWaitlist.end();
244
- for (it = getWaitlist.begin(); it != end; it++) {
245
- const GetWaiter &waiter = *it;
246
- const SuperGroupPtr *superGroup;
247
- assert(!superGroups.lookup(waiter.options.getAppGroupName(), &superGroup));
248
- }
249
- #endif
250
- }
251
-
252
- void fullVerifyInvariants() const {
253
- TRACE_POINT();
254
- verifyInvariants();
255
- UPDATE_TRACE_POINT();
256
- verifyExpensiveInvariants();
257
- UPDATE_TRACE_POINT();
258
-
259
- SuperGroupMap::ConstIterator sg_it(superGroups);
260
- while (*sg_it != NULL) {
261
- const SuperGroupPtr &superGroup = sg_it.getValue();
262
- superGroup->verifyInvariants();
263
- foreach (GroupPtr group, superGroup->groups) {
264
- group->verifyInvariants();
265
- group->verifyExpensiveInvariants();
266
- }
267
- sg_it.next();
268
- }
269
- }
270
-
271
- bool runHookScripts(const char *name,
272
- const boost::function<void (HookScriptOptions &)> &setup) const
273
- {
274
- if (agentsOptions != NULL) {
275
- string hookName = string("hook_") + name;
276
- string spec = agentsOptions->get(hookName, false);
277
- if (!spec.empty()) {
278
- HookScriptOptions options;
279
- options.agentsOptions = agentsOptions;
280
- options.name = name;
281
- options.spec = spec;
282
- setup(options);
283
- return Passenger::runHookScripts(options);
284
- } else {
285
- return true;
286
- }
287
- } else {
288
- return true;
289
- }
290
- }
291
-
292
- static const char *maybePluralize(unsigned int count, const char *singular, const char *plural) {
293
- if (count == 1) {
294
- return singular;
295
- } else {
296
- return plural;
297
- }
298
- }
299
-
300
- const SpawnerConfigPtr &getSpawnerConfig() const {
301
- return spawnerFactory->getConfig();
302
- }
303
-
304
- const UnionStation::CorePtr &getUnionStationCore() const {
305
- return getSpawnerConfig()->unionStationCore;
306
- }
307
-
308
- const RandomGeneratorPtr &getRandomGenerator() const {
309
- return getSpawnerConfig()->randomGenerator;
310
- }
311
-
312
- ProcessPtr findOldestIdleProcess(const Group *exclude = NULL) const {
313
- ProcessPtr oldestIdleProcess;
314
-
315
- SuperGroupMap::ConstIterator sg_it(superGroups);
316
- while (*sg_it != NULL) {
317
- const SuperGroupPtr &superGroup = sg_it.getValue();
318
- const SuperGroup::GroupList &groups = superGroup->groups;
319
- SuperGroup::GroupList::const_iterator g_it, g_end = groups.end();
320
- for (g_it = groups.begin(); g_it != g_end; g_it++) {
321
- const GroupPtr &group = *g_it;
322
- if (group.get() == exclude) {
323
- continue;
324
- }
325
- const ProcessList &processes = group->enabledProcesses;
326
- ProcessList::const_iterator p_it, p_end = processes.end();
327
- for (p_it = processes.begin(); p_it != p_end; p_it++) {
328
- const ProcessPtr process = *p_it;
329
- if (process->busyness() == 0
330
- && (oldestIdleProcess == NULL
331
- || process->lastUsed < oldestIdleProcess->lastUsed)
332
- ) {
333
- oldestIdleProcess = process;
334
- }
335
- }
336
- }
337
- sg_it.next();
338
- }
339
-
340
- return oldestIdleProcess;
341
- }
342
-
343
- ProcessPtr findBestProcessToTrash() const {
344
- ProcessPtr oldestProcess;
345
156
 
346
- SuperGroupMap::ConstIterator sg_it(superGroups);
347
- while (*sg_it != NULL) {
348
- const SuperGroupPtr &superGroup = sg_it.getValue();
349
- const SuperGroup::GroupList &groups = superGroup->groups;
350
- SuperGroup::GroupList::const_iterator g_it, g_end = groups.end();
351
- for (g_it = groups.begin(); g_it != g_end; g_it++) {
352
- const GroupPtr &group = *g_it;
353
- const ProcessList &processes = group->enabledProcesses;
354
- ProcessList::const_iterator p_it, p_end = processes.end();
355
- for (p_it = processes.begin(); p_it != p_end; p_it++) {
356
- const ProcessPtr process = *p_it;
357
- if (oldestProcess == NULL
358
- || process->lastUsed < oldestProcess->lastUsed) {
359
- oldestProcess = process;
360
- }
361
- }
362
- }
363
- sg_it.next();
364
- }
157
+ #include <ApplicationPool2/Pool/AnalyticsCollection.h>
158
+ #include <ApplicationPool2/Pool/GarbageCollection.h>
159
+ #include <ApplicationPool2/Pool/GeneralUtils.h>
160
+ #include <ApplicationPool2/Pool/ProcessUtils.h>
161
+ #include <ApplicationPool2/Pool/Inspection.h>
162
+ #include <ApplicationPool2/Pool/Debug.h>
365
163
 
366
- return oldestProcess;
367
- }
164
+ // Actually private, but marked public so that unit tests can access the fields.
165
+ public:
368
166
 
369
167
  /** Process all waiters on the getWaitlist. Call when capacity has become free.
370
168
  * This function assigns sessions to them by calling get() on the corresponding
@@ -554,47 +352,6 @@ public:
554
352
  }
555
353
  }
556
354
 
557
- void inspectProcessList(const InspectOptions &options, stringstream &result,
558
- const Group *group, const ProcessList &processes) const
559
- {
560
- ProcessList::const_iterator p_it;
561
- for (p_it = processes.begin(); p_it != processes.end(); p_it++) {
562
- const ProcessPtr &process = *p_it;
563
- char buf[128];
564
- char cpubuf[10];
565
- char membuf[10];
566
-
567
- snprintf(cpubuf, sizeof(cpubuf), "%d%%", (int) process->metrics.cpu);
568
- snprintf(membuf, sizeof(membuf), "%ldM",
569
- (unsigned long) (process->metrics.realMemory() / 1024));
570
- snprintf(buf, sizeof(buf),
571
- " * PID: %-5lu Sessions: %-2u Processed: %-5u Uptime: %s\n"
572
- " CPU: %-5s Memory : %-5s Last used: %s ago",
573
- (unsigned long) process->pid,
574
- process->sessions,
575
- process->processed,
576
- process->uptime().c_str(),
577
- cpubuf,
578
- membuf,
579
- distanceOfTimeInWords(process->lastUsed / 1000000).c_str());
580
- result << buf << endl;
581
-
582
- if (process->enabled == Process::DISABLING) {
583
- result << " Disabling..." << endl;
584
- } else if (process->enabled == Process::DISABLED) {
585
- result << " DISABLED" << endl;
586
- } else if (process->enabled == Process::DETACHED) {
587
- result << " Shutting down..." << endl;
588
- }
589
-
590
- const Socket *socket;
591
- if (options.verbose && (socket = process->sockets.findSocketWithName("http")) != NULL) {
592
- result << " URL : http://" << replaceString(socket->address, "tcp://", "") << endl;
593
- result << " Password: " << StaticString(group->secret, Group::SECRET_SIZE) << endl;
594
- }
595
- }
596
- }
597
-
598
355
  struct DetachSuperGroupWaitTicket {
599
356
  boost::mutex syncher;
600
357
  boost::condition_variable cond;
@@ -664,374 +421,12 @@ public:
664
421
  }
665
422
  }
666
423
 
667
- struct GarbageCollectorState {
668
- unsigned long long now;
669
- unsigned long long nextGcRunTime;
670
- boost::container::vector<Callback> actions;
671
- };
672
-
673
- static void garbageCollect(PoolPtr self) {
674
- TRACE_POINT();
675
- {
676
- ScopedLock lock(self->syncher);
677
- self->garbageCollectionCond.timed_wait(lock,
678
- posix_time::seconds(5));
679
- }
680
- while (!this_thread::interruption_requested()) {
681
- try {
682
- UPDATE_TRACE_POINT();
683
- unsigned long long sleepTime = self->realGarbageCollect();
684
- UPDATE_TRACE_POINT();
685
- ScopedLock lock(self->syncher);
686
- self->garbageCollectionCond.timed_wait(lock,
687
- posix_time::microseconds(sleepTime));
688
- } catch (const thread_interrupted &) {
689
- break;
690
- } catch (const tracable_exception &e) {
691
- P_WARN("ERROR: " << e.what() << "\n Backtrace:\n" << e.backtrace());
692
- }
693
- }
694
- }
695
-
696
- void maybeUpdateNextGcRuntime(GarbageCollectorState &state, unsigned long candidate) {
697
- if (state.nextGcRunTime == 0 || candidate < state.nextGcRunTime) {
698
- state.nextGcRunTime = candidate;
699
- }
700
- }
701
-
702
- void checkWhetherProcessCanBeGarbageCollected(GarbageCollectorState &state,
703
- const GroupPtr &group, const ProcessPtr &process, ProcessList &output)
704
- {
705
- assert(maxIdleTime > 0);
706
- unsigned long long processGcTime = process->lastUsed + maxIdleTime;
707
- if (process->sessions == 0
708
- && state.now >= processGcTime
709
- && (unsigned long) group->getProcessCount() > group->options.minProcesses)
710
- {
711
- if (output.capacity() == 0) {
712
- output.reserve(group->enabledCount);
713
- }
714
- output.push_back(process);
715
- } else {
716
- maybeUpdateNextGcRuntime(state, processGcTime);
717
- }
718
- }
719
-
720
- void garbageCollectProcessesInGroup(GarbageCollectorState &state,
721
- const GroupPtr &group)
722
- {
723
- ProcessList &processes = group->enabledProcesses;
724
- ProcessList processesToGc;
725
- ProcessList::iterator p_it, p_end = processes.end();
726
-
727
- for (p_it = processes.begin(); p_it != p_end; p_it++) {
728
- const ProcessPtr &process = *p_it;
729
- checkWhetherProcessCanBeGarbageCollected(state, group, process,
730
- processesToGc);
731
- }
732
-
733
- p_end = processesToGc.end();
734
- for (p_it = processesToGc.begin(); p_it != p_end; p_it++) {
735
- ProcessPtr process = *p_it;
736
- P_DEBUG("Garbage collect idle process: " << process->inspect() <<
737
- ", group=" << group->name);
738
- group->detach(process, state.actions);
739
- }
740
- }
741
-
742
- void maybeCleanPreloader(GarbageCollectorState &state, const GroupPtr &group) {
743
- if (group->spawner->cleanable() && group->options.getMaxPreloaderIdleTime() != 0) {
744
- unsigned long long spawnerGcTime =
745
- group->spawner->lastUsed() +
746
- group->options.getMaxPreloaderIdleTime() * 1000000;
747
- if (state.now >= spawnerGcTime) {
748
- P_DEBUG("Garbage collect idle spawner: group=" << group->name);
749
- group->cleanupSpawner(state.actions);
750
- } else {
751
- maybeUpdateNextGcRuntime(state, spawnerGcTime);
752
- }
753
- }
754
- }
755
-
756
- unsigned long long realGarbageCollect() {
757
- TRACE_POINT();
758
- ScopedLock lock(syncher);
759
- SuperGroupMap::ConstIterator sg_it(superGroups);
760
- GarbageCollectorState state;
761
- state.now = SystemTime::getUsec();
762
- state.nextGcRunTime = 0;
763
-
764
- P_DEBUG("Garbage collection time...");
765
- verifyInvariants();
766
-
767
- // For all supergroups and groups...
768
- while (*sg_it != NULL) {
769
- const SuperGroupPtr superGroup = sg_it.getValue();
770
- SuperGroup::GroupList &groups = superGroup->groups;
771
- SuperGroup::GroupList::iterator g_it, g_end = groups.end();
772
-
773
- superGroup->verifyInvariants();
774
-
775
- for (g_it = groups.begin(); g_it != g_end; g_it++) {
776
- GroupPtr group = *g_it;
777
-
778
- if (maxIdleTime > 0) {
779
- // ...detach processes that have been idle for more than maxIdleTime.
780
- garbageCollectProcessesInGroup(state, group);
781
- }
782
-
783
- group->verifyInvariants();
784
-
785
- // ...cleanup the spawner if it's been idle for more than preloaderIdleTime.
786
- maybeCleanPreloader(state, group);
787
- }
788
-
789
- superGroup->verifyInvariants();
790
- sg_it.next();
791
- }
792
-
793
- verifyInvariants();
794
- lock.unlock();
795
-
796
- // Schedule next garbage collection run.
797
- unsigned long long sleepTime;
798
- if (state.nextGcRunTime == 0 || state.nextGcRunTime <= state.now) {
799
- if (maxIdleTime == 0) {
800
- sleepTime = 10 * 60 * 1000000;
801
- } else {
802
- sleepTime = maxIdleTime;
803
- }
804
- } else {
805
- sleepTime = state.nextGcRunTime - state.now;
806
- }
807
- P_DEBUG("Garbage collection done; next garbage collect in " <<
808
- std::fixed << std::setprecision(3) << (sleepTime / 1000000.0) << " sec");
809
-
810
- UPDATE_TRACE_POINT();
811
- runAllActions(state.actions);
812
- UPDATE_TRACE_POINT();
813
- state.actions.clear();
814
- return sleepTime;
815
- }
816
-
817
- struct UnionStationLogEntry {
818
- string groupName;
819
- const char *category;
820
- string key;
821
- string data;
822
- };
823
-
824
- static void collectAnalytics(PoolPtr self) {
825
- TRACE_POINT();
826
- syscalls::usleep(3000000);
827
- while (!this_thread::interruption_requested()) {
828
- try {
829
- UPDATE_TRACE_POINT();
830
- self->realCollectAnalytics();
831
- } catch (const thread_interrupted &) {
832
- break;
833
- } catch (const tracable_exception &e) {
834
- P_WARN("ERROR: " << e.what() << "\n Backtrace:\n" << e.backtrace());
835
- }
836
-
837
- // Sleep for about 4 seconds, aligned to seconds boundary
838
- // for saving power on laptops.
839
- UPDATE_TRACE_POINT();
840
- unsigned long long currentTime = SystemTime::getUsec();
841
- unsigned long long deadline =
842
- roundUp<unsigned long long>(currentTime, 1000000) + 4000000;
843
- P_DEBUG("Analytics collection done; next analytics collection in " <<
844
- std::fixed << std::setprecision(3) << ((deadline - currentTime) / 1000000.0) <<
845
- " sec");
846
- try {
847
- syscalls::usleep(deadline - currentTime);
848
- } catch (const thread_interrupted &) {
849
- break;
850
- } catch (const tracable_exception &e) {
851
- P_WARN("ERROR: " << e.what() << "\n Backtrace:\n" << e.backtrace());
852
- }
853
- }
854
- }
855
-
856
- static void collectPids(const ProcessList &processes, vector<pid_t> &pids) {
857
- foreach (const ProcessPtr &process, processes) {
858
- pids.push_back(process->pid);
859
- }
860
- }
861
-
862
- static void updateProcessMetrics(const ProcessList &processes,
863
- const ProcessMetricMap &allMetrics,
864
- vector<ProcessPtr> &processesToDetach)
865
- {
866
- foreach (const ProcessPtr &process, processes) {
867
- ProcessMetricMap::const_iterator metrics_it =
868
- allMetrics.find(process->pid);
869
- if (metrics_it != allMetrics.end()) {
870
- process->metrics = metrics_it->second;
871
- // If the process is missing from 'allMetrics' then either 'ps'
872
- // failed or the process really is gone. We double check by sending
873
- // it a signal.
874
- } else if (!process->dummy && !process->osProcessExists()) {
875
- P_WARN("Process " << process->inspect() << " no longer exists! "
876
- "Detaching it from the pool.");
877
- processesToDetach.push_back(process);
878
- }
879
- }
880
- }
881
-
882
- void prepareUnionStationProcessStateLogs(vector<UnionStationLogEntry> &logEntries,
883
- const GroupPtr &group) const
884
- {
885
- const UnionStation::CorePtr &unionStationCore = getUnionStationCore();
886
- if (group->options.analytics && unionStationCore != NULL) {
887
- logEntries.push_back(UnionStationLogEntry());
888
- UnionStationLogEntry &entry = logEntries.back();
889
- stringstream stream;
890
-
891
- stream << "Group: <group>";
892
- group->inspectXml(stream, false);
893
- stream << "</group>";
894
-
895
- entry.groupName = group->options.getAppGroupName();
896
- entry.category = "processes";
897
- entry.key = group->options.unionStationKey;
898
- entry.data = stream.str();
899
- }
900
- }
901
-
902
- void prepareUnionStationSystemMetricsLogs(vector<UnionStationLogEntry> &logEntries,
903
- const GroupPtr &group) const
904
- {
905
- const UnionStation::CorePtr &unionStationCore = getUnionStationCore();
906
- if (group->options.analytics && unionStationCore != NULL) {
907
- logEntries.push_back(UnionStationLogEntry());
908
- UnionStationLogEntry &entry = logEntries.back();
909
- stringstream stream;
910
-
911
- stream << "System metrics: ";
912
- systemMetrics.toXml(stream);
913
-
914
- entry.groupName = group->options.getAppGroupName();
915
- entry.category = "system_metrics";
916
- entry.key = group->options.unionStationKey;
917
- entry.data = stream.str();
918
- }
919
- }
920
-
921
- void realCollectAnalytics() {
922
- TRACE_POINT();
923
- this_thread::disable_interruption di;
924
- this_thread::disable_syscall_interruption dsi;
925
- vector<pid_t> pids;
926
- unsigned int max;
927
-
928
- P_DEBUG("Analytics collection time...");
929
- // Collect all the PIDs.
930
- {
931
- UPDATE_TRACE_POINT();
932
- LockGuard l(syncher);
933
- max = this->max;
934
- }
935
- pids.reserve(max);
936
- {
937
- UPDATE_TRACE_POINT();
938
- LockGuard l(syncher);
939
- SuperGroupMap::ConstIterator sg_it(superGroups);
940
-
941
- while (*sg_it != NULL) {
942
- const SuperGroupPtr &superGroup = sg_it.getValue();
943
- SuperGroup::GroupList::const_iterator g_it, g_end = superGroup->groups.end();
944
-
945
- for (g_it = superGroup->groups.begin(); g_it != g_end; g_it++) {
946
- const GroupPtr &group = *g_it;
947
- collectPids(group->enabledProcesses, pids);
948
- collectPids(group->disablingProcesses, pids);
949
- collectPids(group->disabledProcesses, pids);
950
- }
951
- sg_it.next();
952
- }
953
- }
954
-
955
- // Collect process metrics and system and store them in the
956
- // data structures. Later, we log them to Union Station.
957
- ProcessMetricMap processMetrics;
958
- try {
959
- UPDATE_TRACE_POINT();
960
- processMetrics = ProcessMetricsCollector().collect(pids);
961
- } catch (const ParseException &) {
962
- P_WARN("Unable to collect process metrics: cannot parse 'ps' output.");
963
- return;
964
- }
965
- try {
966
- UPDATE_TRACE_POINT();
967
- systemMetricsCollector.collect(systemMetrics);
968
- } catch (const RuntimeException &e) {
969
- P_WARN("Unable to collect system metrics: " << e.what());
970
- return;
971
- }
972
-
973
- {
974
- UPDATE_TRACE_POINT();
975
- vector<UnionStationLogEntry> logEntries;
976
- vector<ProcessPtr> processesToDetach;
977
- boost::container::vector<Callback> actions;
978
- ScopedLock l(syncher);
979
- SuperGroupMap::ConstIterator sg_it(superGroups);
980
-
981
- UPDATE_TRACE_POINT();
982
- while (*sg_it != NULL) {
983
- const SuperGroupPtr &superGroup = sg_it.getValue();
984
- SuperGroup::GroupList::iterator g_it, g_end = superGroup->groups.end();
985
-
986
- for (g_it = superGroup->groups.begin(); g_it != g_end; g_it++) {
987
- const GroupPtr &group = *g_it;
988
-
989
- updateProcessMetrics(group->enabledProcesses, processMetrics, processesToDetach);
990
- updateProcessMetrics(group->disablingProcesses, processMetrics, processesToDetach);
991
- updateProcessMetrics(group->disabledProcesses, processMetrics, processesToDetach);
992
- prepareUnionStationProcessStateLogs(logEntries, group);
993
- prepareUnionStationSystemMetricsLogs(logEntries, group);
994
- }
995
- sg_it.next();
996
- }
997
-
998
- UPDATE_TRACE_POINT();
999
- foreach (const ProcessPtr process, processesToDetach) {
1000
- detachProcessUnlocked(process, actions);
1001
- }
1002
- UPDATE_TRACE_POINT();
1003
- processesToDetach.clear();
1004
-
1005
- l.unlock();
1006
- UPDATE_TRACE_POINT();
1007
- if (!logEntries.empty()) {
1008
- const UnionStation::CorePtr &unionStationCore = getUnionStationCore();
1009
- while (!logEntries.empty()) {
1010
- UnionStationLogEntry &entry = logEntries.back();
1011
- UnionStation::TransactionPtr transaction =
1012
- unionStationCore->newTransaction(
1013
- entry.groupName,
1014
- entry.category,
1015
- entry.key);
1016
- transaction->message(entry.data);
1017
- logEntries.pop_back();
1018
- }
1019
- }
1020
-
1021
- UPDATE_TRACE_POINT();
1022
- runAllActions(actions);
1023
- UPDATE_TRACE_POINT();
1024
- // Run destructors with updated trace point.
1025
- actions.clear();
1026
- }
1027
- }
1028
-
1029
424
  SuperGroupPtr createSuperGroup(const Options &options) {
1030
425
  SuperGroupPtr superGroup = boost::make_shared<SuperGroup>(this,
1031
426
  options);
1032
427
  superGroup->initialize();
1033
428
  superGroups.insert(options.getAppGroupName(), superGroup);
1034
- garbageCollectionCond.notify_all();
429
+ wakeupGarbageCollector();
1035
430
  return superGroup;
1036
431
  }
1037
432
 
@@ -1093,16 +488,8 @@ public:
1093
488
  /** Must be called right after construction. */
1094
489
  void initialize() {
1095
490
  LockGuard l(syncher);
1096
- interruptableThreads.create_thread(
1097
- boost::bind(collectAnalytics, shared_from_this()),
1098
- "Pool analytics collector",
1099
- POOL_HELPER_THREAD_STACK_SIZE
1100
- );
1101
- interruptableThreads.create_thread(
1102
- boost::bind(garbageCollect, shared_from_this()),
1103
- "Pool garbage collector",
1104
- POOL_HELPER_THREAD_STACK_SIZE
1105
- );
491
+ initializeAnalyticsCollection();
492
+ initializeGarbageCollection();
1106
493
  }
1107
494
 
1108
495
  void initDebugging() {
@@ -1225,7 +612,7 @@ public:
1225
612
  superGroup = boost::make_shared<SuperGroup>(this, options);
1226
613
  superGroup->initialize();
1227
614
  superGroups.insert(options.getAppGroupName(), superGroup);
1228
- garbageCollectionCond.notify_all();
615
+ wakeupGarbageCollector();
1229
616
  SessionPtr session = superGroup->get(options, callback,
1230
617
  actions);
1231
618
  /* The SuperGroup is still initializing so the callback
@@ -1333,7 +720,7 @@ public:
1333
720
  void setMaxIdleTime(unsigned long long value) {
1334
721
  LockGuard l(syncher);
1335
722
  maxIdleTime = value;
1336
- garbageCollectionCond.notify_all();
723
+ wakeupGarbageCollector();
1337
724
  }
1338
725
 
1339
726
  void enableSelfChecking(bool enabled) {
@@ -1622,122 +1009,6 @@ public:
1622
1009
  }
1623
1010
  return false;
1624
1011
  }
1625
-
1626
- string inspect(const InspectOptions &options = InspectOptions(), bool lock = true) const {
1627
- DynamicScopedLock l(syncher, lock);
1628
- stringstream result;
1629
- const char *headerColor = maybeColorize(options, ANSI_COLOR_YELLOW ANSI_COLOR_BLUE_BG ANSI_COLOR_BOLD);
1630
- const char *resetColor = maybeColorize(options, ANSI_COLOR_RESET);
1631
-
1632
- result << headerColor << "----------- General information -----------" << resetColor << endl;
1633
- result << "Max pool size : " << max << endl;
1634
- result << "Processes : " << getProcessCount(false) << endl;
1635
- result << "Requests in top-level queue : " << getWaitlist.size() << endl;
1636
- if (options.verbose) {
1637
- unsigned int i = 0;
1638
- foreach (const GetWaiter &waiter, getWaitlist) {
1639
- result << " " << i << ": " << waiter.options.getAppGroupName() << endl;
1640
- i++;
1641
- }
1642
- }
1643
- result << endl;
1644
-
1645
- result << headerColor << "----------- Application groups -----------" << resetColor << endl;
1646
- SuperGroupMap::ConstIterator sg_it(superGroups);
1647
- while (*sg_it != NULL) {
1648
- const SuperGroupPtr &superGroup = sg_it.getValue();
1649
- const Group *group = superGroup->defaultGroup;
1650
- ProcessList::const_iterator p_it;
1651
-
1652
- if (group != NULL) {
1653
- result << group->name << ":" << endl;
1654
- result << " App root: " << group->options.appRoot << endl;
1655
- if (group->restarting()) {
1656
- result << " (restarting...)" << endl;
1657
- }
1658
- if (group->spawning()) {
1659
- if (group->processesBeingSpawned == 0) {
1660
- result << " (spawning...)" << endl;
1661
- } else {
1662
- result << " (spawning " << group->processesBeingSpawned << " new " <<
1663
- maybePluralize(group->processesBeingSpawned, "process", "processes") <<
1664
- "...)" << endl;
1665
- }
1666
- }
1667
- result << " Requests in queue: " << group->getWaitlist.size() << endl;
1668
- inspectProcessList(options, result, group, group->enabledProcesses);
1669
- inspectProcessList(options, result, group, group->disablingProcesses);
1670
- inspectProcessList(options, result, group, group->disabledProcesses);
1671
- inspectProcessList(options, result, group, group->detachedProcesses);
1672
- result << endl;
1673
- }
1674
- sg_it.next();
1675
- }
1676
- return result.str();
1677
- }
1678
-
1679
- string toXml(bool includeSecrets = true, bool lock = true) const {
1680
- DynamicScopedLock l(syncher, lock);
1681
- stringstream result;
1682
- SuperGroupMap::ConstIterator sg_it(superGroups);
1683
- SuperGroup::GroupList::const_iterator g_it;
1684
- ProcessList::const_iterator p_it;
1685
-
1686
- result << "<?xml version=\"1.0\" encoding=\"iso8859-1\" ?>\n";
1687
- result << "<info version=\"3\">";
1688
-
1689
- result << "<passenger_version>" << PASSENGER_VERSION << "</passenger_version>";
1690
- result << "<process_count>" << getProcessCount(false) << "</process_count>";
1691
- result << "<max>" << max << "</max>";
1692
- result << "<capacity_used>" << capacityUsedUnlocked() << "</capacity_used>";
1693
- result << "<get_wait_list_size>" << getWaitlist.size() << "</get_wait_list_size>";
1694
-
1695
- if (includeSecrets) {
1696
- vector<GetWaiter>::const_iterator w_it, w_end = getWaitlist.end();
1697
-
1698
- result << "<get_wait_list>";
1699
- for (w_it = getWaitlist.begin(); w_it != w_end; w_it++) {
1700
- const GetWaiter &waiter = *w_it;
1701
- result << "<item>";
1702
- result << "<app_group_name>" << escapeForXml(waiter.options.getAppGroupName()) << "</app_group_name>";
1703
- result << "</item>";
1704
- }
1705
- result << "</get_wait_list>";
1706
- }
1707
-
1708
- result << "<supergroups>";
1709
- while (*sg_it != NULL) {
1710
- const SuperGroupPtr &superGroup = sg_it.getValue();
1711
-
1712
- result << "<supergroup>";
1713
- result << "<name>" << escapeForXml(superGroup->name) << "</name>";
1714
- result << "<state>" << superGroup->getStateName() << "</state>";
1715
- result << "<get_wait_list_size>" << superGroup->getWaitlist.size() << "</get_wait_list_size>";
1716
- result << "<capacity_used>" << superGroup->capacityUsed() << "</capacity_used>";
1717
- if (includeSecrets) {
1718
- result << "<secret>" << escapeForXml(superGroup->secret) << "</secret>";
1719
- }
1720
-
1721
- for (g_it = superGroup->groups.begin(); g_it != superGroup->groups.end(); g_it++) {
1722
- const GroupPtr &group = *g_it;
1723
-
1724
- if (group->componentInfo.isDefault) {
1725
- result << "<group default=\"true\">";
1726
- } else {
1727
- result << "<group>";
1728
- }
1729
- group->inspectXml(result, includeSecrets);
1730
- result << "</group>";
1731
- }
1732
- result << "</supergroup>";
1733
-
1734
- sg_it.next();
1735
- }
1736
- result << "</supergroups>";
1737
-
1738
- result << "</info>";
1739
- return result.str();
1740
- }
1741
1012
  };
1742
1013
 
1743
1014