passenger 3.9.2.beta → 4.0.0.rc4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of passenger might be problematic. Click here for more details.
- data/.travis.yml +3 -0
- data/NEWS +77 -7
- data/README.md +3 -11
- data/bin/passenger-install-apache2-module +24 -20
- data/bin/passenger-install-nginx-module +25 -23
- data/build/agents.rb +11 -0
- data/build/apache2.rb +9 -5
- data/build/basics.rb +37 -30
- data/build/common_library.rb +4 -1
- data/build/cplusplus_support.rb +5 -5
- data/build/cxx_tests.rb +28 -8
- data/build/integration_tests.rb +6 -3
- data/build/nginx.rb +3 -3
- data/build/packaging.rb +95 -57
- data/build/ruby_extension.rb +34 -21
- data/build/ruby_tests.rb +4 -2
- data/build/test_basics.rb +1 -1
- data/dev/run_travis.sh +36 -1
- data/doc/Users guide Apache.html +425 -308
- data/doc/Users guide Apache.idmap.txt +78 -70
- data/doc/Users guide Apache.index.sqlite3 +0 -0
- data/doc/Users guide Apache.txt +33 -92
- data/doc/Users guide Nginx.html +519 -220
- data/doc/Users guide Nginx.idmap.txt +78 -60
- data/doc/Users guide Nginx.txt +115 -26
- data/doc/Users guide Standalone.html +8 -2
- data/doc/users_guide_snippets/analysis_and_system_maintenance.txt +1 -7
- data/doc/users_guide_snippets/installation.txt +167 -22
- data/doc/users_guide_snippets/rackup_specifications.txt +4 -0
- data/doc/users_guide_snippets/since_version.txt +1 -0
- data/doc/users_guide_snippets/support_information.txt +3 -7
- data/doc/users_guide_snippets/tips.txt +0 -24
- data/ext/apache2/Configuration.cpp +11 -33
- data/ext/apache2/Configuration.hpp +3 -18
- data/ext/apache2/DirectoryMapper.h +20 -70
- data/ext/apache2/Hooks.cpp +2 -2
- data/ext/common/AgentsStarter.cpp +0 -2
- data/ext/common/AgentsStarter.h +0 -1
- data/ext/common/AgentsStarter.hpp +1 -3
- data/ext/common/ApplicationPool2/AppTypes.cpp +74 -0
- data/ext/common/ApplicationPool2/AppTypes.h +202 -0
- data/ext/common/ApplicationPool2/Common.h +12 -10
- data/ext/common/ApplicationPool2/DirectSpawner.h +256 -0
- data/ext/common/ApplicationPool2/DummySpawner.h +90 -0
- data/ext/common/ApplicationPool2/Group.h +311 -94
- data/ext/common/ApplicationPool2/Implementation.cpp +405 -145
- data/ext/common/ApplicationPool2/Options.h +24 -26
- data/ext/common/ApplicationPool2/PipeWatcher.h +20 -13
- data/ext/common/ApplicationPool2/Pool.h +326 -183
- data/ext/common/ApplicationPool2/Process.h +205 -55
- data/ext/common/ApplicationPool2/README.md +1 -1
- data/ext/common/ApplicationPool2/Session.h +21 -10
- data/ext/common/ApplicationPool2/SmartSpawner.h +801 -0
- data/ext/common/ApplicationPool2/Spawner.h +141 -1149
- data/ext/common/ApplicationPool2/SpawnerFactory.h +132 -0
- data/ext/common/ApplicationPool2/SuperGroup.h +146 -223
- data/ext/common/Constants.h +4 -2
- data/ext/common/Exceptions.h +23 -1
- data/ext/common/Logging.cpp +17 -6
- data/ext/common/Logging.h +37 -7
- data/ext/common/ResourceLocator.h +1 -1
- data/ext/common/Utils.cpp +49 -1
- data/ext/common/Utils.h +13 -4
- data/ext/common/{AnsiColorConstants.h → Utils/AnsiColorConstants.h} +0 -0
- data/ext/common/{BCrypt.cpp → Utils/BCrypt.cpp} +0 -0
- data/ext/common/{BCrypt.h → Utils/BCrypt.h} +0 -0
- data/ext/common/{Blowfish.c → Utils/Blowfish.c} +0 -0
- data/ext/common/{Blowfish.h → Utils/Blowfish.h} +0 -0
- data/ext/common/Utils/CachedFileStat.hpp +27 -25
- data/ext/common/Utils/Curl.h +184 -0
- data/ext/common/{HttpConstants.h → Utils/HttpConstants.h} +3 -0
- data/ext/common/Utils/IOUtils.cpp +6 -2
- data/ext/common/{IniFile.h → Utils/IniFile.h} +0 -0
- data/ext/common/Utils/LargeFiles.cpp +30 -0
- data/ext/common/Utils/LargeFiles.h +40 -0
- data/ext/common/Utils/StrIntUtils.cpp +72 -8
- data/ext/common/Utils/StrIntUtils.h +24 -2
- data/ext/common/Utils/StringMap.h +12 -2
- data/ext/common/Utils/VariantMap.h +51 -2
- data/ext/common/Utils/jsoncpp.cpp +1 -1
- data/ext/common/agents/Base.cpp +147 -11
- data/ext/common/agents/HelperAgent/AgentOptions.h +14 -6
- data/ext/common/agents/HelperAgent/Main.cpp +79 -19
- data/ext/common/agents/HelperAgent/RequestHandler.h +36 -16
- data/ext/common/agents/LoggingAgent/LoggingServer.h +3 -5
- data/ext/common/agents/LoggingAgent/Main.cpp +2 -4
- data/ext/common/agents/LoggingAgent/RemoteSender.h +18 -24
- data/ext/common/agents/SpawnPreparer.cpp +7 -0
- data/ext/common/agents/Watchdog/Main.cpp +96 -38
- data/ext/nginx/Configuration.c +26 -22
- data/ext/nginx/Configuration.h +4 -2
- data/ext/nginx/ContentHandler.c +23 -52
- data/ext/nginx/ContentHandler.h +5 -11
- data/ext/nginx/config +10 -3
- data/ext/nginx/ngx_http_passenger_module.c +21 -6
- data/ext/nginx/ngx_http_passenger_module.h +4 -1
- data/ext/oxt/dynamic_thread_group.hpp +9 -1
- data/ext/oxt/system_calls.cpp +2 -2
- data/ext/ruby/extconf.rb +2 -1
- data/helper-scripts/backtrace-sanitizer.rb +2 -0
- data/helper-scripts/wsgi-loader.py +54 -21
- data/lib/phusion_passenger.rb +5 -3
- data/lib/phusion_passenger/abstract_installer.rb +18 -41
- data/lib/phusion_passenger/admin_tools/memory_stats.rb +2 -2
- data/lib/phusion_passenger/admin_tools/server_instance.rb +2 -2
- data/lib/phusion_passenger/common_library.rb +23 -3
- data/lib/phusion_passenger/debug_logging.rb +10 -3
- data/lib/phusion_passenger/packaging.rb +1 -0
- data/lib/phusion_passenger/platform_info.rb +113 -115
- data/lib/phusion_passenger/platform_info/compiler.rb +224 -134
- data/lib/phusion_passenger/platform_info/cxx_portability.rb +143 -0
- data/lib/phusion_passenger/platform_info/depcheck.rb +371 -0
- data/lib/phusion_passenger/platform_info/depcheck_specs/apache2.rb +124 -0
- data/lib/phusion_passenger/platform_info/depcheck_specs/compiler_toolchain.rb +97 -0
- data/lib/phusion_passenger/platform_info/depcheck_specs/gems.rb +39 -0
- data/lib/phusion_passenger/platform_info/depcheck_specs/libs.rb +118 -0
- data/lib/phusion_passenger/platform_info/depcheck_specs/ruby.rb +137 -0
- data/lib/phusion_passenger/platform_info/depcheck_specs/utilities.rb +15 -0
- data/lib/phusion_passenger/platform_info/operating_system.rb +6 -5
- data/lib/phusion_passenger/platform_info/ruby.rb +45 -34
- data/lib/phusion_passenger/request_handler.rb +35 -22
- data/lib/phusion_passenger/request_handler/thread_handler.rb +5 -6
- data/lib/phusion_passenger/ruby_core_enhancements.rb +7 -1
- data/lib/phusion_passenger/standalone/runtime_installer.rb +43 -34
- data/lib/phusion_passenger/utils/robust_interruption.rb +34 -18
- data/passenger.gemspec +25 -0
- data/resources/templates/standalone/config.erb +3 -1
- data/test/config.json.travis +2 -2
- data/test/cxx/ApplicationPool2/DirectSpawnerTest.cpp +37 -5
- data/test/cxx/ApplicationPool2/PoolTest.cpp +143 -50
- data/test/cxx/ApplicationPool2/ProcessTest.cpp +8 -0
- data/test/cxx/ApplicationPool2/SmartSpawnerTest.cpp +28 -17
- data/test/cxx/ApplicationPool2/SpawnerTestCases.cpp +31 -26
- data/test/cxx/RequestHandlerTest.cpp +17 -1
- data/test/cxx/UtilsTest.cpp +84 -10
- data/test/integration_tests/apache2_tests.rb +49 -163
- data/test/integration_tests/hello_world_wsgi_spec.rb +2 -2
- data/test/integration_tests/mycook_spec.rb +1 -1
- data/test/integration_tests/nginx_tests.rb +37 -19
- data/test/ruby/request_handler_spec.rb +1 -0
- data/test/ruby/spec_helper.rb +52 -1
- data/test/stub/nginx/nginx.conf.erb +2 -0
- data/test/stub/rack/start.rb +5 -0
- data/test/stub/rails3.0/Gemfile.lock +30 -30
- data/test/stub/rails3.1/Gemfile +1 -1
- data/test/stub/rails3.1/Gemfile.lock +3 -3
- data/test/stub/rails3.2/Gemfile +1 -1
- data/test/stub/rails3.2/Gemfile.lock +4 -4
- data/test/stub/rails_apps/2.3/mycook/app/controllers/welcome_controller.rb +1 -1
- data/test/stub/rails_apps/2.3/mycook/app/helpers/recipes_helper.rb +2 -0
- data/test/stub/rails_apps/2.3/mycook/app/helpers/test_helper.rb +2 -0
- data/test/stub/rails_apps/2.3/mycook/app/helpers/uploads_helper.rb +2 -0
- data/test/stub/rails_apps/2.3/mycook/app/helpers/welcome_helper.rb +2 -0
- data/test/support/nginx_controller.rb +2 -1
- metadata +160 -156
- data/build/gempackagetask.rb +0 -99
- data/build/packagetask.rb +0 -186
- data/ext/common/StringListCreator.h +0 -83
- data/lib/phusion_passenger/dependencies.rb +0 -657
@@ -0,0 +1,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
|
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/
|
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
|
-
|
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
|
-
|
91
|
-
|
92
|
-
|
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
|
-
|
99
|
-
|
100
|
-
|
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
|
-
|
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
|
198
|
-
|
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
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
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
|
-
|
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
|
-
*
|
644
|
+
* (lifeStatus == ALIVE) == (spawner != NULL)
|
518
645
|
*/
|
519
|
-
|
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
|
-
|
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
|
-
|
763
|
+
assert(this->superGroup.lock() == NULL);
|
598
764
|
this->superGroup = superGroup;
|
599
765
|
}
|
600
766
|
|
601
|
-
|
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
|
606
|
-
|
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
|
-
|
685
|
-
|
686
|
-
|
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
|
-
|
689
|
-
|
690
|
-
|
881
|
+
foreach (ProcessPtr process, disablingProcesses) {
|
882
|
+
addProcessToList(process, detachedProcesses);
|
883
|
+
process->setShuttingDown();
|
691
884
|
}
|
692
|
-
|
693
|
-
|
694
|
-
|
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
|
775
|
-
|
776
|
-
|
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
|
-
|
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
|
}
|