passenger 5.2.3 → 5.3.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of passenger might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG +13 -0
- data/CONTRIBUTORS +5 -1
- data/build/agent.rb +22 -2
- data/build/cxx_tests.rb +41 -5
- data/build/misc.rb +4 -1
- data/build/support/cxx_dependency_map.rb +1746 -908
- data/build/support/vendor/cxx_hinted_parser/CxxHintedParser.sublime-project +8 -0
- data/build/support/vendor/cxx_hinted_parser/Gemfile +5 -0
- data/build/support/vendor/cxx_hinted_parser/Gemfile.lock +30 -0
- data/{src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core → build/support/vendor/cxx_hinted_parser}/LICENSE.md +1 -1
- data/build/support/vendor/cxx_hinted_parser/README.md +95 -0
- data/build/support/vendor/cxx_hinted_parser/Rakefile +4 -0
- data/{src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/lib/union_station_hooks_rails/initialize.rb → build/support/vendor/cxx_hinted_parser/lib/cxx_hinted_parser.rb} +2 -9
- data/build/support/vendor/cxx_hinted_parser/lib/cxx_hinted_parser/parser.rb +239 -0
- data/dev/ci/README.md +15 -2
- data/dev/ci/lib/set-container-envvars.sh +6 -0
- data/dev/ci/lib/setup-container.sh +4 -1
- data/dev/ci/scripts/debug-console-wrapper.sh +3 -1
- data/dev/ci/setup-host +5 -0
- data/dev/ci/tests/binaries/Jenkinsfile +105 -0
- data/dev/ci/tests/binaries/build-linux +38 -0
- data/dev/ci/tests/binaries/build-macos +40 -0
- data/dev/ci/tests/binaries/prepare-macos +38 -0
- data/dev/ci/tests/binaries/test-linux +45 -0
- data/dev/ci/tests/binaries/test-macos +38 -0
- data/dev/ci/tests/debian/Jenkinsfile +2 -2
- data/dev/ci/tests/rpm/Jenkinsfile +1 -1
- data/dev/configkit-schemas/index.json +3 -24
- data/dev/vagrant/nginx_rakefile +0 -1
- data/package.json +15 -5
- data/resources/templates/error_renderer/.editorconfig +19 -0
- data/resources/templates/error_renderer/with_details/README.md +9 -0
- data/resources/templates/error_renderer/with_details/dist/bundle.js +33 -0
- data/resources/templates/error_renderer/with_details/dist/styles.css +17 -0
- data/resources/templates/error_renderer/with_details/src/DetailsView.jsx +52 -0
- data/resources/templates/error_renderer/with_details/src/GetHelpView.jsx +61 -0
- data/resources/templates/error_renderer/with_details/src/JourneyView.css +50 -0
- data/resources/templates/error_renderer/with_details/src/JourneyView.jsx +621 -0
- data/resources/templates/error_renderer/with_details/src/PageMain.css +114 -0
- data/resources/templates/error_renderer/with_details/src/PageMain.jsx +136 -0
- data/resources/templates/error_renderer/with_details/src/ProblemDescriptionView.jsx +14 -0
- data/resources/templates/error_renderer/with_details/src/ProcessDetailsView.jsx +56 -0
- data/resources/templates/error_renderer/with_details/src/SolutionDescriptionView.css +5 -0
- data/resources/templates/error_renderer/with_details/src/SolutionDescriptionView.jsx +15 -0
- data/resources/templates/error_renderer/with_details/src/SummaryView.jsx +35 -0
- data/resources/templates/error_renderer/with_details/src/SystemComponentView.css +34 -0
- data/resources/templates/error_renderer/with_details/src/SystemComponentView.jsx +168 -0
- data/resources/templates/error_renderer/with_details/src/SystemComponentsView.css +13 -0
- data/resources/templates/error_renderer/with_details/src/SystemComponentsView.jsx +116 -0
- data/resources/templates/error_renderer/with_details/src/Tab.jsx +12 -0
- data/resources/templates/error_renderer/with_details/src/Tabs.jsx +104 -0
- data/resources/templates/error_renderer/with_details/src/bootstrap/bootstrap.css +3446 -0
- data/resources/templates/error_renderer/with_details/src/bootstrap/bootstrap.js +293 -0
- data/resources/templates/error_renderer/with_details/src/bootstrap/config.json +401 -0
- data/resources/templates/error_renderer/with_details/src/index.html.template +22 -0
- data/resources/templates/error_renderer/with_details/src/index.jsx +23 -0
- data/resources/templates/error_renderer/with_details/webpack.config.js +47 -0
- data/resources/templates/error_renderer/without_details/dist/bundle.js +1 -0
- data/resources/templates/error_renderer/without_details/dist/styles.css +1 -0
- data/resources/templates/{undisclosed_error.html.template → error_renderer/without_details/src/index.html.template} +7 -11
- data/resources/templates/error_renderer/without_details/src/index.js +1 -0
- data/resources/templates/{error_layout.css → error_renderer/without_details/src/main.css} +5 -2
- data/resources/templates/error_renderer/without_details/webpack.config.js +42 -0
- data/src/agent/AgentMain.cpp +3 -3
- data/src/agent/Core/ApplicationPool/BasicProcessInfo.h +13 -0
- data/src/agent/Core/ApplicationPool/Common.h +3 -4
- data/src/agent/Core/ApplicationPool/Context.h +27 -17
- data/src/agent/Core/ApplicationPool/Group.h +3 -1
- data/src/agent/Core/ApplicationPool/Group/InitializationAndShutdown.cpp +2 -12
- data/src/agent/Core/ApplicationPool/Group/InternalUtils.cpp +55 -10
- data/src/agent/Core/ApplicationPool/Group/LifetimeAndBasics.cpp +1 -1
- data/src/agent/Core/ApplicationPool/Group/OutOfBandWork.cpp +1 -1
- data/src/agent/Core/ApplicationPool/Group/SpawningAndRestarting.cpp +13 -6
- data/src/agent/Core/ApplicationPool/Implementation.cpp +16 -100
- data/src/agent/Core/ApplicationPool/Options.h +8 -65
- data/src/agent/Core/ApplicationPool/Pool.h +4 -21
- data/src/agent/Core/ApplicationPool/Pool/AnalyticsCollection.cpp +1 -60
- data/src/agent/Core/ApplicationPool/Pool/GeneralUtils.cpp +10 -13
- data/src/agent/Core/ApplicationPool/Pool/InitializationAndShutdown.cpp +3 -8
- data/src/agent/Core/ApplicationPool/Pool/Miscellaneous.cpp +2 -34
- data/src/agent/Core/ApplicationPool/Pool/StateInspection.cpp +1 -1
- data/src/agent/Core/ApplicationPool/Process.cpp +17 -12
- data/src/agent/Core/ApplicationPool/Process.h +146 -93
- data/src/agent/Core/ApplicationPool/Session.h +2 -2
- data/src/agent/Core/ApplicationPool/Socket.h +28 -27
- data/src/agent/Core/Config.h +1 -3
- data/src/agent/Core/ConfigChange.cpp +2 -4
- data/src/agent/Core/Controller.h +2 -8
- data/src/agent/Core/Controller/BufferBody.cpp +0 -2
- data/src/agent/Core/Controller/CheckoutSession.cpp +12 -24
- data/src/agent/Core/Controller/Config.h +1 -9
- data/src/agent/Core/Controller/ForwardResponse.cpp +0 -34
- data/src/agent/Core/Controller/Hooks.cpp +0 -7
- data/src/agent/Core/Controller/InitRequest.cpp +0 -43
- data/src/agent/Core/Controller/InitializationAndShutdown.cpp +0 -4
- data/src/agent/Core/Controller/Request.h +1 -35
- data/src/agent/Core/Controller/SendRequest.cpp +0 -32
- data/src/agent/Core/CoreMain.cpp +19 -32
- data/src/agent/Core/SpawningKit/Config.h +329 -55
- data/src/agent/Core/SpawningKit/Config/AutoGeneratedCode.h +369 -0
- data/src/agent/Core/SpawningKit/Config/AutoGeneratedCode.h.cxxcodebuilder +307 -0
- data/src/agent/Core/SpawningKit/Context.h +211 -0
- data/src/agent/Core/SpawningKit/DirectSpawner.h +112 -122
- data/src/agent/Core/SpawningKit/DummySpawner.h +59 -20
- data/src/agent/Core/SpawningKit/ErrorRenderer.h +117 -0
- data/src/agent/Core/SpawningKit/Exceptions.h +1157 -0
- data/src/agent/Core/SpawningKit/Factory.h +24 -17
- data/src/agent/Core/SpawningKit/{BackgroundIOCapturer.h → Handshake/BackgroundIOCapturer.h} +48 -18
- data/src/agent/Core/SpawningKit/Handshake/Perform.h +1650 -0
- data/src/agent/Core/SpawningKit/Handshake/Prepare.h +582 -0
- data/src/agent/Core/SpawningKit/Handshake/Session.h +91 -0
- data/src/agent/Core/SpawningKit/Handshake/WorkDir.h +100 -0
- data/src/agent/Core/SpawningKit/Journey.h +561 -0
- data/src/agent/Core/SpawningKit/PipeWatcher.h +41 -18
- data/src/agent/Core/SpawningKit/README.md +534 -0
- data/src/agent/Core/SpawningKit/Result.h +182 -7
- data/src/agent/Core/SpawningKit/Result/AutoGeneratedCode.h +69 -0
- data/src/agent/Core/SpawningKit/Result/AutoGeneratedCode.h.cxxcodebuilder +110 -0
- data/src/agent/Core/SpawningKit/SmartSpawner.h +1027 -562
- data/src/agent/Core/SpawningKit/Spawner.h +70 -1134
- data/src/agent/Core/SpawningKit/UserSwitchingRules.h +3 -33
- data/src/agent/README.md +2 -3
- data/src/agent/Shared/ApiServerUtils.h +2 -3
- data/src/agent/SpawnEnvSetupper/SpawnEnvSetupperMain.cpp +932 -0
- data/src/agent/Watchdog/Config.h +1 -3
- data/src/agent/Watchdog/WatchdogMain.cpp +2 -1
- data/src/apache2_module/ConfigGeneral/AutoGeneratedDefinitions.cpp +5 -0
- data/src/apache2_module/ConfigGeneral/AutoGeneratedManifestDefaultsInitialization.cpp +5 -0
- data/src/apache2_module/ConfigGeneral/ManifestGeneration.h +22 -13
- data/src/apache2_module/DirConfig/AutoGeneratedCreateFunction.cpp +5 -0
- data/src/apache2_module/DirConfig/AutoGeneratedHeaderSerialization.cpp +3 -0
- data/src/apache2_module/DirConfig/AutoGeneratedManifestGeneration.cpp +13 -0
- data/src/apache2_module/DirConfig/AutoGeneratedMergeFunction.cpp +7 -0
- data/src/apache2_module/DirConfig/AutoGeneratedStruct.h +13 -0
- data/src/cxx_supportlib/Constants.h +3 -1
- data/src/cxx_supportlib/Exceptions.h +0 -121
- data/src/cxx_supportlib/LoggingKit/Implementation.cpp +7 -6
- data/src/cxx_supportlib/LoggingKit/Logging.h +3 -1
- data/src/cxx_supportlib/Utils.cpp +42 -0
- data/src/cxx_supportlib/Utils.h +7 -0
- data/src/cxx_supportlib/Utils/IOUtils.cpp +58 -0
- data/src/cxx_supportlib/Utils/IOUtils.h +13 -0
- data/src/cxx_supportlib/Utils/JsonUtils.h +130 -23
- data/src/cxx_supportlib/Utils/ScopeGuard.h +9 -4
- data/src/cxx_supportlib/Utils/StrIntUtils.cpp +7 -0
- data/src/cxx_supportlib/Utils/StrIntUtils.h +1 -0
- data/src/cxx_supportlib/Utils/SystemTime.h +1 -1
- data/src/cxx_supportlib/Utils/Timer.h +1 -1
- data/src/cxx_supportlib/WebSocketCommandReverseServer.h +6 -4
- data/src/cxx_supportlib/vendor-copy/adhoc_lve.h +1 -0
- data/src/helper-scripts/node-loader.js +54 -59
- data/src/helper-scripts/rack-loader.rb +63 -60
- data/src/helper-scripts/rack-preloader.rb +125 -72
- data/src/helper-scripts/wsgi-loader.py +100 -43
- data/src/nginx_module/ConfigGeneral/AutoGeneratedDefinitions.c +120 -112
- data/src/nginx_module/ConfigGeneral/AutoGeneratedManifestDefaultsInitialization.c +15 -8
- data/src/nginx_module/ConfigGeneral/AutoGeneratedSetterFuncs.c +142 -142
- data/src/nginx_module/ConfigGeneral/ManifestGeneration.c +26 -15
- data/src/nginx_module/ConfigGeneral/ManifestGeneration.h +3 -0
- data/src/nginx_module/LocationConfig/AutoGeneratedCreateFunction.c +76 -70
- data/src/nginx_module/LocationConfig/AutoGeneratedHeaderSerialization.c +114 -99
- data/src/nginx_module/LocationConfig/AutoGeneratedManifestGeneration.c +170 -156
- data/src/nginx_module/LocationConfig/AutoGeneratedMergeFunction.c +38 -35
- data/src/nginx_module/LocationConfig/AutoGeneratedStruct.h +5 -1
- data/src/ruby_supportlib/phusion_passenger.rb +5 -5
- data/src/ruby_supportlib/phusion_passenger/admin_tools/instance.rb +14 -1
- data/src/ruby_supportlib/phusion_passenger/apache2/config_options.rb +8 -0
- data/src/ruby_supportlib/phusion_passenger/common_library.rb +0 -3
- data/src/ruby_supportlib/phusion_passenger/config/nginx_engine_compiler.rb +0 -1
- data/src/ruby_supportlib/phusion_passenger/constants.rb +2 -0
- data/src/ruby_supportlib/phusion_passenger/loader_shared_helpers.rb +646 -238
- data/src/ruby_supportlib/phusion_passenger/nginx/config_options.rb +117 -95
- data/src/ruby_supportlib/phusion_passenger/packaging.rb +0 -1
- data/src/ruby_supportlib/phusion_passenger/platform_info/depcheck_specs/apache2.rb +5 -1
- data/src/ruby_supportlib/phusion_passenger/preloader_shared_helpers.rb +92 -69
- data/src/ruby_supportlib/phusion_passenger/public_api.rb +0 -17
- data/src/ruby_supportlib/phusion_passenger/rack/thread_handler_extension.rb +0 -3
- data/src/ruby_supportlib/phusion_passenger/request_handler.rb +4 -5
- data/src/ruby_supportlib/phusion_passenger/request_handler/thread_handler.rb +0 -22
- metadata +64 -67
- data/resources/templates/error_layout.html.template +0 -86
- data/resources/templates/general_error.html.template +0 -1
- data/resources/templates/general_error_with_html.html.template +0 -1
- data/src/agent/Core/ApplicationPool/ErrorRenderer.h +0 -131
- data/src/agent/Core/SpawningKit/Options.h +0 -41
- data/src/agent/Core/UnionStation/Connection.h +0 -173
- data/src/agent/Core/UnionStation/Context.h +0 -536
- data/src/agent/Core/UnionStation/StopwatchLog.h +0 -147
- data/src/agent/Core/UnionStation/Transaction.h +0 -249
- data/src/agent/SpawnPreparer/SpawnPreparerMain.cpp +0 -208
- data/src/cxx_supportlib/UnionStationFilterSupport.cpp +0 -67
- data/src/cxx_supportlib/UnionStationFilterSupport.h +0 -1622
- data/src/nodejs_supportlib/phusion_passenger/log_express.js +0 -106
- data/src/nodejs_supportlib/phusion_passenger/log_mongodb.js +0 -202
- data/src/nodejs_supportlib/phusion_passenger/ustreporter.js +0 -227
- data/src/nodejs_supportlib/phusion_passenger/ustrouter_connector.js +0 -448
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/CONFIG.md +0 -37
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/Gemfile +0 -17
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/Gemfile.lock +0 -59
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/README-API.md +0 -5
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/README.md +0 -117
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/Rakefile +0 -115
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core.rb +0 -423
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/api.rb +0 -238
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/connection.rb +0 -67
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/context.rb +0 -281
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/lock.rb +0 -62
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/log.rb +0 -66
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/message_channel.rb +0 -157
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/request_reporter.rb +0 -150
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/request_reporter/basics.rb +0 -199
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/request_reporter/controllers.rb +0 -187
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/request_reporter/misc.rb +0 -303
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/request_reporter/view_rendering.rb +0 -91
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/simple_json.rb +0 -396
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/spec_helper.rb +0 -279
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/time_point.rb +0 -39
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/transaction.rb +0 -173
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/utils.rb +0 -177
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/version.rb +0 -32
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/version_data.rb +0 -44
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/ruby_versions.yml.example +0 -16
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/ruby_versions.yml.travis +0 -20
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/ruby_versions.yml.travis-with-sudo +0 -18
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/union_station_hooks_core.gemspec +0 -23
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/Gemfile +0 -14
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/Gemfile.lock +0 -45
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/LICENSE.md +0 -19
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/README.md +0 -104
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/Rakefile +0 -160
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/lib/union_station_hooks_rails.rb +0 -200
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/lib/union_station_hooks_rails/action_controller_extension.rb +0 -45
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/lib/union_station_hooks_rails/action_view_subscriber.rb +0 -55
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/lib/union_station_hooks_rails/active_record_subscriber.rb +0 -41
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/lib/union_station_hooks_rails/active_support_benchmarkable_extension.rb +0 -47
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/lib/union_station_hooks_rails/active_support_cache_subscriber.rb +0 -79
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/lib/union_station_hooks_rails/exception_logger.rb +0 -57
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/lib/union_station_hooks_rails/version.rb +0 -32
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/lib/union_station_hooks_rails/version_data.rb +0 -44
- data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/union_station_hooks_rails.gemspec +0 -34
@@ -26,7 +26,7 @@
|
|
26
26
|
#ifndef _PASSENGER_SPAWNING_KIT_FACTORY_H_
|
27
27
|
#define _PASSENGER_SPAWNING_KIT_FACTORY_H_
|
28
28
|
|
29
|
-
#include <Core/SpawningKit/
|
29
|
+
#include <Core/SpawningKit/Context.h>
|
30
30
|
#include <Core/SpawningKit/SmartSpawner.h>
|
31
31
|
#include <Core/SpawningKit/DirectSpawner.h>
|
32
32
|
#include <Core/SpawningKit/DummySpawner.h>
|
@@ -42,11 +42,11 @@ using namespace oxt;
|
|
42
42
|
class Factory {
|
43
43
|
private:
|
44
44
|
boost::mutex syncher;
|
45
|
-
|
45
|
+
Context *context;
|
46
46
|
DummySpawnerPtr dummySpawner;
|
47
47
|
|
48
|
-
SpawnerPtr tryCreateSmartSpawner(const
|
49
|
-
string dir =
|
48
|
+
SpawnerPtr tryCreateSmartSpawner(const AppPoolOptions &options) {
|
49
|
+
string dir = context->resourceLocator->getHelperScriptsDir();
|
50
50
|
vector<string> preloaderCommand;
|
51
51
|
if (options.appType == "rack") {
|
52
52
|
preloaderCommand.push_back(options.ruby);
|
@@ -54,30 +54,37 @@ private:
|
|
54
54
|
} else {
|
55
55
|
return SpawnerPtr();
|
56
56
|
}
|
57
|
-
return boost::make_shared<SmartSpawner>(
|
58
|
-
|
57
|
+
return boost::make_shared<SmartSpawner>(context,
|
58
|
+
preloaderCommand, options);
|
59
59
|
}
|
60
60
|
|
61
61
|
public:
|
62
|
-
|
63
|
-
|
64
|
-
|
62
|
+
unsigned int spawnerCreationSleepTime;
|
63
|
+
|
64
|
+
Factory(Context *_context)
|
65
|
+
: context(_context),
|
66
|
+
spawnerCreationSleepTime(0)
|
67
|
+
{
|
68
|
+
if (context->debugSupport != NULL) {
|
69
|
+
spawnerCreationSleepTime = context->debugSupport->spawnerCreationSleepTime;
|
70
|
+
}
|
71
|
+
}
|
65
72
|
|
66
73
|
virtual ~Factory() { }
|
67
74
|
|
68
|
-
virtual SpawnerPtr create(const
|
75
|
+
virtual SpawnerPtr create(const AppPoolOptions &options) {
|
69
76
|
if (options.spawnMethod == "smart" || options.spawnMethod == "smart-lv2") {
|
70
77
|
SpawnerPtr spawner = tryCreateSmartSpawner(options);
|
71
78
|
if (spawner == NULL) {
|
72
|
-
spawner = boost::make_shared<DirectSpawner>(
|
79
|
+
spawner = boost::make_shared<DirectSpawner>(context);
|
73
80
|
}
|
74
81
|
return spawner;
|
75
82
|
} else if (options.spawnMethod == "direct" || options.spawnMethod == "conservative") {
|
76
83
|
boost::shared_ptr<DirectSpawner> spawner = boost::make_shared<DirectSpawner>(
|
77
|
-
|
84
|
+
context);
|
78
85
|
return spawner;
|
79
86
|
} else if (options.spawnMethod == "dummy") {
|
80
|
-
syscalls::usleep(
|
87
|
+
syscalls::usleep(spawnerCreationSleepTime);
|
81
88
|
return getDummySpawner();
|
82
89
|
} else {
|
83
90
|
throw ArgumentException("Unknown spawn method '" + options.spawnMethod + "'");
|
@@ -92,16 +99,16 @@ public:
|
|
92
99
|
DummySpawnerPtr getDummySpawner() {
|
93
100
|
boost::lock_guard<boost::mutex> l(syncher);
|
94
101
|
if (dummySpawner == NULL) {
|
95
|
-
dummySpawner = boost::make_shared<DummySpawner>(
|
102
|
+
dummySpawner = boost::make_shared<DummySpawner>(context);
|
96
103
|
}
|
97
104
|
return dummySpawner;
|
98
105
|
}
|
99
106
|
|
100
107
|
/**
|
101
|
-
* All created Spawner objects share the same
|
108
|
+
* All created Spawner objects share the same Context object.
|
102
109
|
*/
|
103
|
-
|
104
|
-
return
|
110
|
+
Context *getContext() const {
|
111
|
+
return context;
|
105
112
|
}
|
106
113
|
};
|
107
114
|
|
@@ -28,6 +28,7 @@
|
|
28
28
|
|
29
29
|
#include <boost/thread.hpp>
|
30
30
|
#include <boost/bind.hpp>
|
31
|
+
#include <boost/function.hpp>
|
31
32
|
#include <boost/foreach.hpp>
|
32
33
|
#include <oxt/backtrace.hpp>
|
33
34
|
#include <oxt/system_calls.hpp>
|
@@ -57,14 +58,16 @@ using namespace std;
|
|
57
58
|
*/
|
58
59
|
class BackgroundIOCapturer {
|
59
60
|
private:
|
60
|
-
FileDescriptor fd;
|
61
|
-
pid_t pid;
|
62
|
-
const
|
63
|
-
|
61
|
+
const FileDescriptor fd;
|
62
|
+
const pid_t pid;
|
63
|
+
const string appGroupName;
|
64
|
+
const string appLogFile;
|
65
|
+
const StaticString channelName;
|
66
|
+
mutable boost::mutex dataSyncher;
|
64
67
|
string data;
|
65
68
|
oxt::thread *thr;
|
66
|
-
|
67
|
-
|
69
|
+
boost::function<void ()> endReachedCallback;
|
70
|
+
bool stopped;
|
68
71
|
|
69
72
|
void capture() {
|
70
73
|
TRACE_POINT();
|
@@ -104,16 +107,30 @@ private:
|
|
104
107
|
}
|
105
108
|
}
|
106
109
|
}
|
110
|
+
|
111
|
+
{
|
112
|
+
boost::lock_guard<boost::mutex> l(dataSyncher);
|
113
|
+
stopped = true;
|
114
|
+
}
|
115
|
+
if (endReachedCallback != NULL) {
|
116
|
+
endReachedCallback();
|
117
|
+
}
|
107
118
|
}
|
108
119
|
|
109
120
|
public:
|
110
|
-
BackgroundIOCapturer(const FileDescriptor &_fd, pid_t _pid,
|
121
|
+
BackgroundIOCapturer(const FileDescriptor &_fd, pid_t _pid,
|
122
|
+
const string &_appGroupName,
|
123
|
+
const string &_appLogFile,
|
124
|
+
const StaticString &_channelName = P_STATIC_STRING("output"),
|
125
|
+
const StaticString &_data = StaticString())
|
111
126
|
: fd(_fd),
|
112
127
|
pid(_pid),
|
128
|
+
appGroupName(_appGroupName),
|
129
|
+
appLogFile(_appLogFile),
|
113
130
|
channelName(_channelName),
|
131
|
+
data(_data.data(), _data.size()),
|
114
132
|
thr(NULL),
|
115
|
-
|
116
|
-
appLogFile(_appLogFile)
|
133
|
+
stopped(false)
|
117
134
|
{ }
|
118
135
|
|
119
136
|
~BackgroundIOCapturer() {
|
@@ -137,16 +154,19 @@ public:
|
|
137
154
|
"Background I/O capturer", 64 * 1024);
|
138
155
|
}
|
139
156
|
|
140
|
-
|
157
|
+
void stop() {
|
141
158
|
TRACE_POINT();
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
159
|
+
if (thr != NULL) {
|
160
|
+
boost::this_thread::disable_interruption di;
|
161
|
+
boost::this_thread::disable_syscall_interruption dsi;
|
162
|
+
thr->interrupt_and_join();
|
163
|
+
delete thr;
|
164
|
+
thr = NULL;
|
165
|
+
}
|
166
|
+
}
|
167
|
+
|
168
|
+
void setEndReachedCallback(const boost::function<void ()> &callback) {
|
169
|
+
endReachedCallback = callback;
|
150
170
|
}
|
151
171
|
|
152
172
|
void appendToBuffer(const StaticString &dataToAdd) {
|
@@ -154,6 +174,16 @@ public:
|
|
154
174
|
boost::lock_guard<boost::mutex> l(dataSyncher);
|
155
175
|
data.append(dataToAdd.data(), dataToAdd.size());
|
156
176
|
}
|
177
|
+
|
178
|
+
string getData() const {
|
179
|
+
boost::lock_guard<boost::mutex> l(dataSyncher);
|
180
|
+
return data;
|
181
|
+
}
|
182
|
+
|
183
|
+
bool isStopped() const {
|
184
|
+
boost::lock_guard<boost::mutex> l(dataSyncher);
|
185
|
+
return stopped;
|
186
|
+
}
|
157
187
|
};
|
158
188
|
|
159
189
|
typedef boost::shared_ptr<BackgroundIOCapturer> BackgroundIOCapturerPtr;
|
@@ -0,0 +1,1650 @@
|
|
1
|
+
/*
|
2
|
+
* Phusion Passenger - https://www.phusionpassenger.com/
|
3
|
+
* Copyright (c) 2016-2017 Phusion Holding B.V.
|
4
|
+
*
|
5
|
+
* "Passenger", "Phusion Passenger" and "Union Station" are registered
|
6
|
+
* trademarks of Phusion Holding B.V.
|
7
|
+
*
|
8
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
9
|
+
* of this software and associated documentation files (the "Software"), to deal
|
10
|
+
* in the Software without restriction, including without limitation the rights
|
11
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
12
|
+
* copies of the Software, and to permit persons to whom the Software is
|
13
|
+
* furnished to do so, subject to the following conditions:
|
14
|
+
*
|
15
|
+
* The above copyright notice and this permission notice shall be included in
|
16
|
+
* all copies or substantial portions of the Software.
|
17
|
+
*
|
18
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
19
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
20
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
21
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
22
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
23
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
24
|
+
* THE SOFTWARE.
|
25
|
+
*/
|
26
|
+
#ifndef _PASSENGER_SPAWNING_KIT_HANDSHAKE_PERFORM_H_
|
27
|
+
#define _PASSENGER_SPAWNING_KIT_HANDSHAKE_PERFORM_H_
|
28
|
+
|
29
|
+
#include <boost/thread.hpp>
|
30
|
+
#include <boost/make_shared.hpp>
|
31
|
+
#include <boost/bind.hpp>
|
32
|
+
#include <oxt/thread.hpp>
|
33
|
+
#include <oxt/system_calls.hpp>
|
34
|
+
#include <oxt/backtrace.hpp>
|
35
|
+
#include <string>
|
36
|
+
#include <vector>
|
37
|
+
#include <stdexcept>
|
38
|
+
#include <cstddef>
|
39
|
+
#include <cstdlib>
|
40
|
+
#include <cerrno>
|
41
|
+
#include <cassert>
|
42
|
+
|
43
|
+
#include <sys/types.h>
|
44
|
+
#include <dirent.h>
|
45
|
+
|
46
|
+
#include <jsoncpp/json.h>
|
47
|
+
|
48
|
+
#include <Constants.h>
|
49
|
+
#include <Exceptions.h>
|
50
|
+
#include <FileDescriptor.h>
|
51
|
+
#include <Utils.h>
|
52
|
+
#include <Utils/ScopeGuard.h>
|
53
|
+
#include <Utils/SystemTime.h>
|
54
|
+
#include <Utils/StrIntUtils.h>
|
55
|
+
#include <Core/SpawningKit/Config.h>
|
56
|
+
#include <Core/SpawningKit/Exceptions.h>
|
57
|
+
#include <Core/SpawningKit/Handshake/BackgroundIOCapturer.h>
|
58
|
+
#include <Core/SpawningKit/Handshake/Session.h>
|
59
|
+
|
60
|
+
namespace Passenger {
|
61
|
+
namespace SpawningKit {
|
62
|
+
|
63
|
+
using namespace std;
|
64
|
+
using namespace oxt;
|
65
|
+
|
66
|
+
|
67
|
+
/**
|
68
|
+
* For an introduction see README.md, section
|
69
|
+
* "The handshake and the HandshakePerform class".
|
70
|
+
*/
|
71
|
+
class HandshakePerform {
|
72
|
+
private:
|
73
|
+
enum FinishState {
|
74
|
+
// The app hasn't finished spawning yet.
|
75
|
+
NOT_FINISHED,
|
76
|
+
// The app has successfully finished spawning.
|
77
|
+
FINISH_SUCCESS,
|
78
|
+
// The app has finished spawning with an error.
|
79
|
+
FINISH_ERROR,
|
80
|
+
// An internal error occurred in watchFinishSignal().
|
81
|
+
FINISH_INTERNAL_ERROR
|
82
|
+
};
|
83
|
+
|
84
|
+
HandshakeSession &session;
|
85
|
+
Config * const config;
|
86
|
+
const pid_t pid;
|
87
|
+
const FileDescriptor stdinFd;
|
88
|
+
const FileDescriptor stdoutAndErrFd;
|
89
|
+
const string alreadyReadStdoutAndErrData;
|
90
|
+
|
91
|
+
|
92
|
+
/**
|
93
|
+
* These objects captures the process's stdout and stderr while handshake is
|
94
|
+
* in progress. If handshaking fails, then any output captured by these objects
|
95
|
+
* will be stored into the resulting SpawnException's error page.
|
96
|
+
*/
|
97
|
+
BackgroundIOCapturerPtr stdoutAndErrCapturer;
|
98
|
+
|
99
|
+
boost::mutex syncher;
|
100
|
+
boost::condition_variable cond;
|
101
|
+
|
102
|
+
oxt::thread *processExitWatcher;
|
103
|
+
oxt::thread *finishSignalWatcher;
|
104
|
+
bool processExited;
|
105
|
+
FinishState finishState;
|
106
|
+
string finishSignalWatcherErrorMessage;
|
107
|
+
ErrorCategory finishSignalWatcherErrorCategory;
|
108
|
+
|
109
|
+
oxt::thread *socketPingabilityWatcher;
|
110
|
+
bool socketIsNowPingable;
|
111
|
+
|
112
|
+
|
113
|
+
void initializeStdchannelsCapturing() {
|
114
|
+
if (stdoutAndErrFd != -1) {
|
115
|
+
stdoutAndErrCapturer = boost::make_shared<BackgroundIOCapturer>(
|
116
|
+
stdoutAndErrFd, pid, "output", alreadyReadStdoutAndErrData);
|
117
|
+
stdoutAndErrCapturer->setEndReachedCallback(boost::bind(
|
118
|
+
&HandshakePerform::wakeupEventLoop, this));
|
119
|
+
stdoutAndErrCapturer->start();
|
120
|
+
}
|
121
|
+
}
|
122
|
+
|
123
|
+
void startWatchingProcessExit() {
|
124
|
+
processExitWatcher = new oxt::thread(
|
125
|
+
boost::bind(&HandshakePerform::watchProcessExit, this),
|
126
|
+
"SpawningKit: process exit watcher", 64 * 1024);
|
127
|
+
}
|
128
|
+
|
129
|
+
void watchProcessExit() {
|
130
|
+
TRACE_POINT();
|
131
|
+
int ret = syscalls::waitpid(pid, NULL, 0);
|
132
|
+
if (ret >= 0 || errno == EPERM) {
|
133
|
+
boost::lock_guard<boost::mutex> l(syncher);
|
134
|
+
processExited = true;
|
135
|
+
wakeupEventLoop();
|
136
|
+
}
|
137
|
+
}
|
138
|
+
|
139
|
+
void startWatchingFinishSignal() {
|
140
|
+
finishSignalWatcher = new oxt::thread(
|
141
|
+
boost::bind(&HandshakePerform::watchFinishSignal, this),
|
142
|
+
"SpawningKit: finish signal watcher", 64 * 1024);
|
143
|
+
}
|
144
|
+
|
145
|
+
void watchFinishSignal() {
|
146
|
+
TRACE_POINT();
|
147
|
+
try {
|
148
|
+
string path = session.responseDir + "/finish";
|
149
|
+
int fd = syscalls::open(path.c_str(), O_RDONLY);
|
150
|
+
if (fd == -1) {
|
151
|
+
int e = errno;
|
152
|
+
throw FileSystemException("Error opening FIFO " + path,
|
153
|
+
e, path);
|
154
|
+
}
|
155
|
+
FdGuard guard(fd, __FILE__, __LINE__);
|
156
|
+
|
157
|
+
char buf = '0';
|
158
|
+
ssize_t ret = syscalls::read(fd, &buf, 1);
|
159
|
+
if (ret == -1) {
|
160
|
+
int e = errno;
|
161
|
+
throw FileSystemException("Error reading from FIFO " + path,
|
162
|
+
e, path);
|
163
|
+
}
|
164
|
+
|
165
|
+
guard.runNow();
|
166
|
+
|
167
|
+
boost::lock_guard<boost::mutex> l(syncher);
|
168
|
+
if (buf == '1') {
|
169
|
+
finishState = FINISH_SUCCESS;
|
170
|
+
} else {
|
171
|
+
finishState = FINISH_ERROR;
|
172
|
+
}
|
173
|
+
wakeupEventLoop();
|
174
|
+
} catch (const std::exception &e) {
|
175
|
+
boost::lock_guard<boost::mutex> l(syncher);
|
176
|
+
finishState = FINISH_INTERNAL_ERROR;
|
177
|
+
finishSignalWatcherErrorMessage = e.what();
|
178
|
+
finishSignalWatcherErrorCategory =
|
179
|
+
inferErrorCategoryFromAnotherException(e,
|
180
|
+
SPAWNING_KIT_HANDSHAKE_PERFORM);
|
181
|
+
wakeupEventLoop();
|
182
|
+
}
|
183
|
+
}
|
184
|
+
|
185
|
+
void startWatchingSocketPingability() {
|
186
|
+
socketPingabilityWatcher = new oxt::thread(
|
187
|
+
boost::bind(&HandshakePerform::watchSocketPingability, this),
|
188
|
+
"SpawningKit: socket pingability watcher", 64 * 1024);
|
189
|
+
}
|
190
|
+
|
191
|
+
void watchSocketPingability() {
|
192
|
+
TRACE_POINT();
|
193
|
+
|
194
|
+
while (true) {
|
195
|
+
unsigned long long timeout = 100000;
|
196
|
+
|
197
|
+
if (pingTcpServer("127.0.0.1", session.expectedStartPort, &timeout)) {
|
198
|
+
boost::lock_guard<boost::mutex> l(syncher);
|
199
|
+
socketIsNowPingable = true;
|
200
|
+
finishState = FINISH_SUCCESS;
|
201
|
+
wakeupEventLoop();
|
202
|
+
} else {
|
203
|
+
syscalls::usleep(50000);
|
204
|
+
}
|
205
|
+
}
|
206
|
+
}
|
207
|
+
|
208
|
+
void waitUntilSpawningFinished(boost::unique_lock<boost::mutex> &l) {
|
209
|
+
TRACE_POINT();
|
210
|
+
bool done;
|
211
|
+
|
212
|
+
do {
|
213
|
+
boost::this_thread::interruption_point();
|
214
|
+
done = checkCurrentState();
|
215
|
+
if (!done) {
|
216
|
+
MonotonicTimeUsec begin = SystemTime::getMonotonicUsec();
|
217
|
+
cond.timed_wait(l, posix_time::microseconds(session.timeoutUsec));
|
218
|
+
MonotonicTimeUsec end = SystemTime::getMonotonicUsec();
|
219
|
+
if (end - begin > session.timeoutUsec) {
|
220
|
+
session.timeoutUsec = 0;
|
221
|
+
} else {
|
222
|
+
session.timeoutUsec -= end - begin;
|
223
|
+
}
|
224
|
+
}
|
225
|
+
} while (!done);
|
226
|
+
}
|
227
|
+
|
228
|
+
bool checkCurrentState() {
|
229
|
+
TRACE_POINT();
|
230
|
+
|
231
|
+
if ((stdoutAndErrCapturer != NULL && stdoutAndErrCapturer->isStopped())
|
232
|
+
|| processExited)
|
233
|
+
{
|
234
|
+
UPDATE_TRACE_POINT();
|
235
|
+
sleepShortlyToCaptureMoreStdoutStderr();
|
236
|
+
loadJourneyStateFromResponseDir();
|
237
|
+
if (session.journey.getFirstFailedStep() == UNKNOWN_JOURNEY_STEP) {
|
238
|
+
session.journey.setStepErrored(bestGuessSubprocessFailedStep(), true);
|
239
|
+
}
|
240
|
+
|
241
|
+
SpawnException e(
|
242
|
+
inferErrorCategoryFromResponseDir(INTERNAL_ERROR),
|
243
|
+
session.journey,
|
244
|
+
config);
|
245
|
+
e.setSummary("The application process exited prematurely.");
|
246
|
+
e.setSubprocessPid(pid);
|
247
|
+
e.setStdoutAndErrData(getStdoutErrData());
|
248
|
+
loadSubprocessErrorMessagesAndEnvDump(e);
|
249
|
+
throw e.finalize();
|
250
|
+
}
|
251
|
+
|
252
|
+
if (session.timeoutUsec == 0) {
|
253
|
+
UPDATE_TRACE_POINT();
|
254
|
+
sleepShortlyToCaptureMoreStdoutStderr();
|
255
|
+
|
256
|
+
loadJourneyStateFromResponseDir();
|
257
|
+
session.journey.setStepErrored(SPAWNING_KIT_HANDSHAKE_PERFORM);
|
258
|
+
|
259
|
+
SpawnException e(TIMEOUT_ERROR, session.journey, config);
|
260
|
+
e.setSubprocessPid(pid);
|
261
|
+
e.setStdoutAndErrData(getStdoutErrData());
|
262
|
+
loadSubprocessErrorMessagesAndEnvDump(e);
|
263
|
+
throw e.finalize();
|
264
|
+
}
|
265
|
+
|
266
|
+
return (config->genericApp && socketIsNowPingable)
|
267
|
+
|| (!config->genericApp && finishState != NOT_FINISHED);
|
268
|
+
}
|
269
|
+
|
270
|
+
Result handleResponse() {
|
271
|
+
TRACE_POINT();
|
272
|
+
switch (finishState) {
|
273
|
+
case FINISH_SUCCESS:
|
274
|
+
return handleSuccessResponse();
|
275
|
+
case FINISH_ERROR:
|
276
|
+
handleErrorResponse();
|
277
|
+
return Result(); // Never reached, shut up compiler warning.
|
278
|
+
case FINISH_INTERNAL_ERROR:
|
279
|
+
handleInternalError();
|
280
|
+
return Result(); // Never reached, shut up compiler warning.
|
281
|
+
default:
|
282
|
+
P_BUG("Unknown finishState " + toString((int) finishState));
|
283
|
+
return Result(); // Never reached, shut up compiler warning.
|
284
|
+
}
|
285
|
+
}
|
286
|
+
|
287
|
+
Result handleSuccessResponse() {
|
288
|
+
TRACE_POINT();
|
289
|
+
Result &result = session.result;
|
290
|
+
vector<StaticString> internalFieldErrors, appSuppliedFieldErrors;
|
291
|
+
|
292
|
+
result.pid = pid;
|
293
|
+
result.stdinFd = stdinFd;
|
294
|
+
result.stdoutAndErrFd = stdoutAndErrFd;
|
295
|
+
result.spawnEndTime = SystemTime::getUsec();
|
296
|
+
result.spawnEndTimeMonotonic = SystemTime::getMonotonicUsec();
|
297
|
+
|
298
|
+
if (socketIsNowPingable) {
|
299
|
+
assert(config->genericApp || config->findFreePort);
|
300
|
+
result.sockets.push_back(Result::Socket());
|
301
|
+
Result::Socket &socket = result.sockets.back();
|
302
|
+
socket.address = "tcp://127.0.0.1:" + toString(session.expectedStartPort);
|
303
|
+
socket.protocol = "http";
|
304
|
+
socket.concurrency = -1;
|
305
|
+
socket.acceptHttpRequests = true;
|
306
|
+
}
|
307
|
+
|
308
|
+
UPDATE_TRACE_POINT();
|
309
|
+
if (fileExists(session.responseDir + "/properties.json")) {
|
310
|
+
loadResultPropertiesFromResponseDir(!socketIsNowPingable);
|
311
|
+
|
312
|
+
UPDATE_TRACE_POINT();
|
313
|
+
if (session.journey.getType() == START_PRELOADER
|
314
|
+
&& !resultHasSocketWithPreloaderProtocol())
|
315
|
+
{
|
316
|
+
throwSpawnExceptionBecauseAppDidNotProvidePreloaderProtocolSockets();
|
317
|
+
} else if (session.journey.getType() != START_PRELOADER
|
318
|
+
&& !resultHasSocketThatAcceptsHttpRequests())
|
319
|
+
{
|
320
|
+
throwSpawnExceptionBecauseAppDidNotProvideSocketsThatAcceptRequests();
|
321
|
+
}
|
322
|
+
}
|
323
|
+
|
324
|
+
UPDATE_TRACE_POINT();
|
325
|
+
if (result.validate(internalFieldErrors, appSuppliedFieldErrors)) {
|
326
|
+
return result;
|
327
|
+
} else {
|
328
|
+
throwSpawnExceptionBecauseOfResultValidationErrors(internalFieldErrors,
|
329
|
+
appSuppliedFieldErrors);
|
330
|
+
abort(); // never reached, shut up compiler warning
|
331
|
+
}
|
332
|
+
}
|
333
|
+
|
334
|
+
void handleErrorResponse() {
|
335
|
+
TRACE_POINT();
|
336
|
+
sleepShortlyToCaptureMoreStdoutStderr();
|
337
|
+
loadJourneyStateFromResponseDir();
|
338
|
+
if (session.journey.getFirstFailedStep() == UNKNOWN_JOURNEY_STEP) {
|
339
|
+
session.journey.setStepErrored(bestGuessSubprocessFailedStep(), true);
|
340
|
+
}
|
341
|
+
|
342
|
+
SpawnException e(
|
343
|
+
inferErrorCategoryFromResponseDir(INTERNAL_ERROR),
|
344
|
+
session.journey,
|
345
|
+
config);
|
346
|
+
e.setSummary("The web application aborted with an error during startup.");
|
347
|
+
e.setSubprocessPid(pid);
|
348
|
+
e.setStdoutAndErrData(getStdoutErrData());
|
349
|
+
loadSubprocessErrorMessagesAndEnvDump(e);
|
350
|
+
throw e.finalize();
|
351
|
+
}
|
352
|
+
|
353
|
+
void handleInternalError() {
|
354
|
+
TRACE_POINT();
|
355
|
+
sleepShortlyToCaptureMoreStdoutStderr();
|
356
|
+
|
357
|
+
loadJourneyStateFromResponseDir();
|
358
|
+
session.journey.setStepErrored(SPAWNING_KIT_HANDSHAKE_PERFORM);
|
359
|
+
|
360
|
+
SpawnException e(
|
361
|
+
finishSignalWatcherErrorCategory,
|
362
|
+
session.journey,
|
363
|
+
config);
|
364
|
+
e.setSummary("An internal error occurred while spawning an application process: "
|
365
|
+
+ finishSignalWatcherErrorMessage);
|
366
|
+
e.setAdvancedProblemDetails(finishSignalWatcherErrorMessage);
|
367
|
+
e.setSubprocessPid(pid);
|
368
|
+
e.setStdoutAndErrData(getStdoutErrData());
|
369
|
+
throw e.finalize();
|
370
|
+
}
|
371
|
+
|
372
|
+
void loadResultPropertiesFromResponseDir(bool socketsRequired) {
|
373
|
+
Result &result = session.result;
|
374
|
+
string path = session.responseDir + "/properties.json";
|
375
|
+
Json::Reader reader;
|
376
|
+
Json::Value doc;
|
377
|
+
vector<string> errors;
|
378
|
+
|
379
|
+
// We already checked whether properties.json exists before invoking
|
380
|
+
// this method, so if readAll() fails then we can't be sure that
|
381
|
+
// it's an application problem. This is why we want the SystemException
|
382
|
+
// to propagate to higher layers so that there it can be turned into
|
383
|
+
// a generic filesystem-related or IO-related SpawnException, as opposed
|
384
|
+
// to one about this problem specifically.
|
385
|
+
|
386
|
+
if (!reader.parse(readAll(path), doc)) {
|
387
|
+
errors.push_back("Error parsing " + path + ": " +
|
388
|
+
reader.getFormattedErrorMessages());
|
389
|
+
throwSpawnExceptionBecauseOfResultValidationErrors(vector<string>(),
|
390
|
+
errors);
|
391
|
+
}
|
392
|
+
|
393
|
+
validateResultPropertiesFile(doc, socketsRequired, errors);
|
394
|
+
if (!errors.empty()) {
|
395
|
+
errors.insert(errors.begin(), "The following errors were detected in "
|
396
|
+
+ path + ":");
|
397
|
+
throwSpawnExceptionBecauseOfResultValidationErrors(vector<string>(),
|
398
|
+
errors);
|
399
|
+
}
|
400
|
+
|
401
|
+
if (!socketsRequired && (!doc.isMember("sockets") || doc["sockets"].empty())) {
|
402
|
+
return;
|
403
|
+
}
|
404
|
+
|
405
|
+
Json::Value::iterator it, end = doc["sockets"].end();
|
406
|
+
for (it = doc["sockets"].begin(); it != end; it++) {
|
407
|
+
const Json::Value &socketDoc = *it;
|
408
|
+
result.sockets.push_back(Result::Socket());
|
409
|
+
Result::Socket &socket = result.sockets.back();
|
410
|
+
|
411
|
+
socket.address = socketDoc["address"].asString();
|
412
|
+
socket.protocol = socketDoc["protocol"].asString();
|
413
|
+
socket.concurrency = socketDoc["concurrency"].asInt();
|
414
|
+
if (socketDoc.isMember("accept_http_requests")) {
|
415
|
+
socket.acceptHttpRequests = socketDoc["accept_http_requests"].asBool();
|
416
|
+
}
|
417
|
+
if (socketDoc.isMember("description")) {
|
418
|
+
socket.description = socketDoc["description"].asString();
|
419
|
+
}
|
420
|
+
}
|
421
|
+
}
|
422
|
+
|
423
|
+
void validateResultPropertiesFile(const Json::Value &doc, bool socketsRequired,
|
424
|
+
vector<string> &errors) const
|
425
|
+
{
|
426
|
+
if (!doc.isMember("sockets")) {
|
427
|
+
if (socketsRequired) {
|
428
|
+
errors.push_back("'sockets' must be specified");
|
429
|
+
}
|
430
|
+
} else if (!doc["sockets"].isArray()) {
|
431
|
+
errors.push_back("'sockets' must be an array");
|
432
|
+
} else {
|
433
|
+
if (socketsRequired && doc["sockets"].empty()) {
|
434
|
+
errors.push_back("'sockets' must be non-empty");
|
435
|
+
return;
|
436
|
+
}
|
437
|
+
|
438
|
+
Json::Value::const_iterator it, end = doc["sockets"].end();
|
439
|
+
for (it = doc["sockets"].begin(); it != end; it++) {
|
440
|
+
const Json::Value &socketDoc = *it;
|
441
|
+
|
442
|
+
if (!socketDoc.isObject()) {
|
443
|
+
errors.push_back("'sockets[" + toString(it.index())
|
444
|
+
+ "]' must be an object");
|
445
|
+
continue;
|
446
|
+
}
|
447
|
+
|
448
|
+
validateResultPropertiesFileSocketField(socketDoc,
|
449
|
+
"address", Json::stringValue, it.index(),
|
450
|
+
true, true, errors);
|
451
|
+
validateResultPropertiesFileSocketField(socketDoc,
|
452
|
+
"protocol", Json::stringValue, it.index(),
|
453
|
+
true, true, errors);
|
454
|
+
validateResultPropertiesFileSocketField(socketDoc,
|
455
|
+
"description", Json::stringValue, it.index(),
|
456
|
+
false, true, errors);
|
457
|
+
validateResultPropertiesFileSocketField(socketDoc,
|
458
|
+
"concurrency", Json::intValue, it.index(),
|
459
|
+
true, false, errors);
|
460
|
+
validateResultPropertiesFileSocketField(socketDoc,
|
461
|
+
"accept_http_requests", Json::booleanValue, it.index(),
|
462
|
+
false, false, errors);
|
463
|
+
}
|
464
|
+
}
|
465
|
+
}
|
466
|
+
|
467
|
+
void validateResultPropertiesFileSocketField(const Json::Value &doc,
|
468
|
+
const char *key, Json::ValueType type, unsigned int index, bool required,
|
469
|
+
bool requireNonEmpty, vector<string> &errors) const
|
470
|
+
{
|
471
|
+
if (!doc.isMember(key)) {
|
472
|
+
if (required) {
|
473
|
+
errors.push_back("'sockets[" + toString(index)
|
474
|
+
+ "]." + key + "' must be specified");
|
475
|
+
}
|
476
|
+
} else if (doc[key].type() != type) {
|
477
|
+
const char *typeDesc;
|
478
|
+
switch (type) {
|
479
|
+
case Json::stringValue:
|
480
|
+
typeDesc = "a string";
|
481
|
+
break;
|
482
|
+
case Json::intValue:
|
483
|
+
typeDesc = "an integer";
|
484
|
+
break;
|
485
|
+
case Json::booleanValue:
|
486
|
+
typeDesc = "a boolean";
|
487
|
+
break;
|
488
|
+
default:
|
489
|
+
typeDesc = "(unknown type)";
|
490
|
+
break;
|
491
|
+
}
|
492
|
+
errors.push_back("'sockets[" + toString(index)
|
493
|
+
+ "]." + key + "' must be " + typeDesc);
|
494
|
+
} else if (requireNonEmpty && doc[key].asString().empty()) {
|
495
|
+
errors.push_back("'sockets[" + toString(index)
|
496
|
+
+ "]." + key + "' must be non-empty");
|
497
|
+
}
|
498
|
+
}
|
499
|
+
|
500
|
+
bool resultHasSocketWithPreloaderProtocol() const {
|
501
|
+
const vector<Result::Socket> &sockets = session.result.sockets;
|
502
|
+
vector<Result::Socket>::const_iterator it, end = sockets.end();
|
503
|
+
for (it = sockets.begin(); it != end; it++) {
|
504
|
+
if (it->protocol == "preloader") {
|
505
|
+
return true;
|
506
|
+
}
|
507
|
+
}
|
508
|
+
return false;
|
509
|
+
}
|
510
|
+
|
511
|
+
bool resultHasSocketThatAcceptsHttpRequests() const {
|
512
|
+
const vector<Result::Socket> &sockets = session.result.sockets;
|
513
|
+
vector<Result::Socket>::const_iterator it, end = sockets.end();
|
514
|
+
for (it = sockets.begin(); it != end; it++) {
|
515
|
+
if (it->acceptHttpRequests) {
|
516
|
+
return true;
|
517
|
+
}
|
518
|
+
}
|
519
|
+
return false;
|
520
|
+
}
|
521
|
+
|
522
|
+
void wakeupEventLoop() {
|
523
|
+
cond.notify_all();
|
524
|
+
}
|
525
|
+
|
526
|
+
string getStdoutErrData() const {
|
527
|
+
return getStdoutErrData(stdoutAndErrCapturer);
|
528
|
+
}
|
529
|
+
|
530
|
+
static string getStdoutErrData(const BackgroundIOCapturerPtr &stdoutAndErrCapturer) {
|
531
|
+
if (stdoutAndErrCapturer != NULL) {
|
532
|
+
return stdoutAndErrCapturer->getData();
|
533
|
+
} else {
|
534
|
+
return "(not available)";
|
535
|
+
}
|
536
|
+
}
|
537
|
+
|
538
|
+
void sleepShortlyToCaptureMoreStdoutStderr() const {
|
539
|
+
syscalls::usleep(50000);
|
540
|
+
}
|
541
|
+
|
542
|
+
void throwSpawnExceptionBecauseAppDidNotProvidePreloaderProtocolSockets() {
|
543
|
+
TRACE_POINT();
|
544
|
+
assert(!config->genericApp);
|
545
|
+
|
546
|
+
sleepShortlyToCaptureMoreStdoutStderr();
|
547
|
+
|
548
|
+
if (!config->genericApp && config->startsUsingWrapper) {
|
549
|
+
UPDATE_TRACE_POINT();
|
550
|
+
loadJourneyStateFromResponseDir();
|
551
|
+
session.journey.setStepErrored(SUBPROCESS_WRAPPER_PREPARATION, true);
|
552
|
+
|
553
|
+
SpawnException e(INTERNAL_ERROR, session.journey, config);
|
554
|
+
e.setSubprocessPid(pid);
|
555
|
+
e.setStdoutAndErrData(getStdoutErrData());
|
556
|
+
loadAnnotationsFromEnvDumpDir(e);
|
557
|
+
|
558
|
+
if (config->wrapperSuppliedByThirdParty) {
|
559
|
+
e.setSummary("Error spawning the web application:"
|
560
|
+
" a third-party application wrapper did not"
|
561
|
+
" report any sockets to receive preloader commands on.");
|
562
|
+
} else {
|
563
|
+
e.setSummary("Error spawning the web application:"
|
564
|
+
" a " SHORT_PROGRAM_NAME "-internal application"
|
565
|
+
" wrapper did not report any sockets to receive"
|
566
|
+
" preloader commands on.");
|
567
|
+
}
|
568
|
+
|
569
|
+
if (config->wrapperSuppliedByThirdParty) {
|
570
|
+
e.setProblemDescriptionHTML(
|
571
|
+
"<p>The " PROGRAM_NAME " application server tried"
|
572
|
+
" to start the web application through a helper tool "
|
573
|
+
" called the \"wrapper\". This helper tool is not part of "
|
574
|
+
SHORT_PROGRAM_NAME ". " SHORT_PROGRAM_NAME " expected"
|
575
|
+
" the helper tool to report a socket to receive preloader"
|
576
|
+
" commands on, but the helper tool finished its startup"
|
577
|
+
" procedure without reporting such a socket.</p>");
|
578
|
+
} else {
|
579
|
+
e.setProblemDescriptionHTML(
|
580
|
+
"<p>The " PROGRAM_NAME " application server tried"
|
581
|
+
" to start the web application through a " SHORT_PROGRAM_NAME
|
582
|
+
"-internal helper tool called the \"wrapper\","
|
583
|
+
" but " SHORT_PROGRAM_NAME " encountered a bug"
|
584
|
+
" in this helper tool. " SHORT_PROGRAM_NAME " expected"
|
585
|
+
" the helper tool to report a socket to receive preloader"
|
586
|
+
" commands on, but the helper tool finished its startup"
|
587
|
+
" procedure without reporting such a socket.</p>");
|
588
|
+
}
|
589
|
+
|
590
|
+
if (config->wrapperSuppliedByThirdParty) {
|
591
|
+
e.setSolutionDescriptionHTML(
|
592
|
+
"<p class=\"sole-solution\">"
|
593
|
+
"This is a bug in the wrapper, so please contact the author of"
|
594
|
+
" the wrapper. This problem is outside " SHORT_PROGRAM_NAME
|
595
|
+
"'s control. Below follows the command that "
|
596
|
+
SHORT_PROGRAM_NAME " tried to execute, so that you can infer"
|
597
|
+
" which wrapper was used:</p>"
|
598
|
+
"<pre>" + escapeHTML(config->startCommand) + "</pre>");
|
599
|
+
} else {
|
600
|
+
e.setSolutionDescriptionHTML(
|
601
|
+
"<p class=\"sole-solution\">"
|
602
|
+
"This is a bug in " SHORT_PROGRAM_NAME "."
|
603
|
+
" <a href=\"" SUPPORT_URL "\">Please report this bug</a>"
|
604
|
+
" to the " SHORT_PROGRAM_NAME " authors.</p>");
|
605
|
+
}
|
606
|
+
|
607
|
+
throw e.finalize();
|
608
|
+
|
609
|
+
} else {
|
610
|
+
UPDATE_TRACE_POINT();
|
611
|
+
loadJourneyStateFromResponseDir();
|
612
|
+
session.journey.setStepErrored(SUBPROCESS_APP_LOAD_OR_EXEC, true);
|
613
|
+
|
614
|
+
SpawnException e(INTERNAL_ERROR, session.journey, config);
|
615
|
+
e.setSubprocessPid(pid);
|
616
|
+
e.setStdoutAndErrData(getStdoutErrData());
|
617
|
+
loadAnnotationsFromEnvDumpDir(e);
|
618
|
+
|
619
|
+
e.setSummary("Error spawning the web application: the application"
|
620
|
+
" did not report any sockets to receive preloader commands on.");
|
621
|
+
e.setProblemDescriptionHTML(
|
622
|
+
"<p>The " PROGRAM_NAME " application server tried"
|
623
|
+
" to start the web application, but encountered a bug"
|
624
|
+
" in the application. " SHORT_PROGRAM_NAME " expected"
|
625
|
+
" the application to report a socket to receive preloader"
|
626
|
+
" commands on, but the application finished its startup"
|
627
|
+
" procedure without reporting such a socket.</p>");
|
628
|
+
e.setSolutionDescriptionHTML(
|
629
|
+
"<p class=\"sole-solution\">"
|
630
|
+
"Since this is a bug in the web application, please "
|
631
|
+
"report this problem to the application's developer. "
|
632
|
+
"This problem is outside " SHORT_PROGRAM_NAME "'s "
|
633
|
+
"control.</p>");
|
634
|
+
|
635
|
+
throw e.finalize();
|
636
|
+
}
|
637
|
+
}
|
638
|
+
|
639
|
+
void throwSpawnExceptionBecauseAppDidNotProvideSocketsThatAcceptRequests() {
|
640
|
+
TRACE_POINT();
|
641
|
+
assert(!config->genericApp);
|
642
|
+
|
643
|
+
sleepShortlyToCaptureMoreStdoutStderr();
|
644
|
+
|
645
|
+
if (!config->genericApp && config->startsUsingWrapper) {
|
646
|
+
UPDATE_TRACE_POINT();
|
647
|
+
loadJourneyStateFromResponseDir();
|
648
|
+
switch (session.journey.getType()) {
|
649
|
+
case SPAWN_DIRECTLY:
|
650
|
+
case START_PRELOADER:
|
651
|
+
session.journey.setStepErrored(SUBPROCESS_WRAPPER_PREPARATION, true);
|
652
|
+
break;
|
653
|
+
case SPAWN_THROUGH_PRELOADER:
|
654
|
+
session.journey.setStepErrored(SUBPROCESS_PREPARE_AFTER_FORKING_FROM_PRELOADER, true);
|
655
|
+
break;
|
656
|
+
default:
|
657
|
+
P_BUG("Unknown journey type " << (int) session.journey.getType());
|
658
|
+
}
|
659
|
+
|
660
|
+
SpawnException e(INTERNAL_ERROR, session.journey, config);
|
661
|
+
e.setSubprocessPid(pid);
|
662
|
+
e.setStdoutAndErrData(getStdoutErrData());
|
663
|
+
loadAnnotationsFromEnvDumpDir(e);
|
664
|
+
|
665
|
+
if (config->wrapperSuppliedByThirdParty) {
|
666
|
+
e.setSummary("Error spawning the web application:"
|
667
|
+
" a third-party application wrapper did not"
|
668
|
+
" report any sockets to receive requests on.");
|
669
|
+
} else {
|
670
|
+
e.setSummary("Error spawning the web application:"
|
671
|
+
" a " SHORT_PROGRAM_NAME "-internal application"
|
672
|
+
" wrapper did not report any sockets to receive"
|
673
|
+
" requests on.");
|
674
|
+
}
|
675
|
+
|
676
|
+
if (config->wrapperSuppliedByThirdParty) {
|
677
|
+
e.setProblemDescriptionHTML(
|
678
|
+
"<p>The " PROGRAM_NAME " application server tried"
|
679
|
+
" to start the web application through a helper tool"
|
680
|
+
" called the \"wrapper\". This helper tool is not part of "
|
681
|
+
SHORT_PROGRAM_NAME ". " SHORT_PROGRAM_NAME " expected"
|
682
|
+
" the helper tool to report a socket to receive requests"
|
683
|
+
" on, but the helper tool finished its startup procedure"
|
684
|
+
" without reporting such a socket.</p>");
|
685
|
+
} else {
|
686
|
+
e.setProblemDescriptionHTML(
|
687
|
+
"<p>The " PROGRAM_NAME " application server tried"
|
688
|
+
" to start the web application through a " SHORT_PROGRAM_NAME
|
689
|
+
"-internal helper tool called the \"wrapper\","
|
690
|
+
" but " SHORT_PROGRAM_NAME " encountered a bug"
|
691
|
+
" in this helper tool. " SHORT_PROGRAM_NAME " expected"
|
692
|
+
" the helper tool to report a socket to receive requests"
|
693
|
+
" on, but the helper tool finished its startup procedure"
|
694
|
+
" without reporting such a socket.</p>");
|
695
|
+
}
|
696
|
+
|
697
|
+
if (config->wrapperSuppliedByThirdParty) {
|
698
|
+
e.setSolutionDescriptionHTML(
|
699
|
+
"<p class=\"sole-solution\">"
|
700
|
+
"This is a bug in the wrapper, so please contact the author of"
|
701
|
+
" the wrapper. This problem is outside " SHORT_PROGRAM_NAME
|
702
|
+
"'s control. Below follows the command that "
|
703
|
+
SHORT_PROGRAM_NAME " tried to execute, so that you can infer"
|
704
|
+
" which wrapper was used:</p>"
|
705
|
+
"<pre>" + escapeHTML(config->startCommand) + "</pre>");
|
706
|
+
} else {
|
707
|
+
e.setSolutionDescriptionHTML(
|
708
|
+
"<p class=\"sole-solution\">"
|
709
|
+
"This is a bug in " SHORT_PROGRAM_NAME "."
|
710
|
+
" <a href=\"" SUPPORT_URL "\">Please report this bug</a>"
|
711
|
+
" to the " SHORT_PROGRAM_NAME " authors.</p>");
|
712
|
+
}
|
713
|
+
|
714
|
+
throw e.finalize();
|
715
|
+
|
716
|
+
} else {
|
717
|
+
UPDATE_TRACE_POINT();
|
718
|
+
loadJourneyStateFromResponseDir();
|
719
|
+
switch (session.journey.getType()) {
|
720
|
+
case SPAWN_DIRECTLY:
|
721
|
+
case START_PRELOADER:
|
722
|
+
session.journey.setStepErrored(SUBPROCESS_APP_LOAD_OR_EXEC, true);
|
723
|
+
break;
|
724
|
+
case SPAWN_THROUGH_PRELOADER:
|
725
|
+
session.journey.setStepErrored(SUBPROCESS_PREPARE_AFTER_FORKING_FROM_PRELOADER, true);
|
726
|
+
break;
|
727
|
+
default:
|
728
|
+
P_BUG("Unknown journey type " << (int) session.journey.getType());
|
729
|
+
}
|
730
|
+
|
731
|
+
SpawnException e(INTERNAL_ERROR, session.journey, config);
|
732
|
+
e.setSubprocessPid(pid);
|
733
|
+
e.setStdoutAndErrData(getStdoutErrData());
|
734
|
+
loadAnnotationsFromEnvDumpDir(e);
|
735
|
+
|
736
|
+
e.setSummary("Error spawning the web application: the application"
|
737
|
+
" did not report any sockets to receive requests on.");
|
738
|
+
e.setProblemDescriptionHTML(
|
739
|
+
"<p>The " PROGRAM_NAME " application server tried"
|
740
|
+
" to start the web application, but encountered a bug"
|
741
|
+
" in the application. " SHORT_PROGRAM_NAME " expected"
|
742
|
+
" the application to report a socket to receive requests"
|
743
|
+
" on, but the application finished its startup procedure"
|
744
|
+
" without reporting such a socket.</p>");
|
745
|
+
e.setSolutionDescriptionHTML(
|
746
|
+
"<p class=\"sole-solution\">"
|
747
|
+
"Since this is a bug in the web application, please "
|
748
|
+
"report this problem to the application's developer. "
|
749
|
+
"This problem is outside " SHORT_PROGRAM_NAME "'s "
|
750
|
+
"control.</p>");
|
751
|
+
|
752
|
+
throw e.finalize();
|
753
|
+
}
|
754
|
+
}
|
755
|
+
|
756
|
+
template<typename StringType>
|
757
|
+
void throwSpawnExceptionBecauseOfResultValidationErrors(
|
758
|
+
const vector<StringType> &internalFieldErrors,
|
759
|
+
const vector<StringType> &appSuppliedFieldErrors)
|
760
|
+
{
|
761
|
+
TRACE_POINT();
|
762
|
+
string message;
|
763
|
+
typename vector<StringType>::const_iterator it, end;
|
764
|
+
|
765
|
+
sleepShortlyToCaptureMoreStdoutStderr();
|
766
|
+
|
767
|
+
if (!internalFieldErrors.empty()) {
|
768
|
+
UPDATE_TRACE_POINT();
|
769
|
+
loadJourneyStateFromResponseDir();
|
770
|
+
session.journey.setStepErrored(SPAWNING_KIT_HANDSHAKE_PERFORM, true);
|
771
|
+
|
772
|
+
SpawnException e(INTERNAL_ERROR, session.journey, config);
|
773
|
+
e.setSubprocessPid(pid);
|
774
|
+
e.setStdoutAndErrData(getStdoutErrData());
|
775
|
+
e.setAdvancedProblemDetails(toString(internalFieldErrors));
|
776
|
+
|
777
|
+
e.setSummary("Error spawning the web application:"
|
778
|
+
" a bug in " SHORT_PROGRAM_NAME " caused the"
|
779
|
+
" spawn result to be invalid: "
|
780
|
+
+ toString(internalFieldErrors));
|
781
|
+
|
782
|
+
message = "<p>The " PROGRAM_NAME " application server tried"
|
783
|
+
" to start the web application, but encountered a bug"
|
784
|
+
" in " SHORT_PROGRAM_NAME " itself. The errors are as"
|
785
|
+
" follows:</p>"
|
786
|
+
"<ul>";
|
787
|
+
end = internalFieldErrors.end();
|
788
|
+
for (it = internalFieldErrors.begin(); it != end; it++) {
|
789
|
+
message.append("<li>" + escapeHTML(*it) + "</li>");
|
790
|
+
}
|
791
|
+
message.append("</ul>");
|
792
|
+
e.setProblemDescriptionHTML(message);
|
793
|
+
|
794
|
+
e.setSolutionDescriptionHTML(
|
795
|
+
"<p class=\"sole-solution\">"
|
796
|
+
"This is a bug in " SHORT_PROGRAM_NAME "."
|
797
|
+
" <a href=\"" SUPPORT_URL "\">Please report this bug</a>"
|
798
|
+
" to the " SHORT_PROGRAM_NAME " authors.</p>");
|
799
|
+
|
800
|
+
throw e.finalize();
|
801
|
+
|
802
|
+
} else if (!config->genericApp && config->startsUsingWrapper) {
|
803
|
+
UPDATE_TRACE_POINT();
|
804
|
+
loadJourneyStateFromResponseDir();
|
805
|
+
session.journey.setStepErrored(SUBPROCESS_WRAPPER_PREPARATION, true);
|
806
|
+
|
807
|
+
SpawnException e(INTERNAL_ERROR, session.journey, config);
|
808
|
+
e.setSubprocessPid(pid);
|
809
|
+
e.setStdoutAndErrData(getStdoutErrData());
|
810
|
+
e.setAdvancedProblemDetails(toString(appSuppliedFieldErrors));
|
811
|
+
loadAnnotationsFromEnvDumpDir(e);
|
812
|
+
|
813
|
+
if (config->wrapperSuppliedByThirdParty) {
|
814
|
+
e.setSummary("Error spawning the web application:"
|
815
|
+
" a bug in a third-party application wrapper caused"
|
816
|
+
" the spawn result to be invalid: "
|
817
|
+
+ toString(appSuppliedFieldErrors));
|
818
|
+
} else {
|
819
|
+
e.setSummary("Error spawning the web application:"
|
820
|
+
" a bug in a " SHORT_PROGRAM_NAME "-internal"
|
821
|
+
" application wrapper caused the"
|
822
|
+
" spawn result to be invalid: "
|
823
|
+
+ toString(appSuppliedFieldErrors));
|
824
|
+
}
|
825
|
+
|
826
|
+
if (config->wrapperSuppliedByThirdParty) {
|
827
|
+
message = "<p>The " PROGRAM_NAME " application server tried"
|
828
|
+
" to start the web application through a helper tool"
|
829
|
+
" called the \"wrapper\". This helper tool is not part of "
|
830
|
+
SHORT_PROGRAM_NAME ". " SHORT_PROGRAM_NAME " expected"
|
831
|
+
" the helper tool to communicate back various information"
|
832
|
+
" about the application's startup procedure, but the tool"
|
833
|
+
" did not communicate back correctly."
|
834
|
+
" The errors are as follows:</p>"
|
835
|
+
"<ul>";
|
836
|
+
} else {
|
837
|
+
message = "<p>The " PROGRAM_NAME " application server tried"
|
838
|
+
" to start the web application through a " SHORT_PROGRAM_NAME
|
839
|
+
"-internal helper tool (called the \"wrapper\"),"
|
840
|
+
" but " SHORT_PROGRAM_NAME " encountered a bug"
|
841
|
+
" in this helper tool. " SHORT_PROGRAM_NAME " expected"
|
842
|
+
" the helper tool to communicate back various information"
|
843
|
+
" about the application's startup procedure, but the tool"
|
844
|
+
" did not communicate back correctly."
|
845
|
+
" The errors are as follows:</p>"
|
846
|
+
"<ul>";
|
847
|
+
}
|
848
|
+
end = appSuppliedFieldErrors.end();
|
849
|
+
for (it = appSuppliedFieldErrors.begin(); it != end; it++) {
|
850
|
+
message.append("<li>" + escapeHTML(*it) + "</li>");
|
851
|
+
}
|
852
|
+
message.append("</ul>");
|
853
|
+
e.setProblemDescriptionHTML(message);
|
854
|
+
|
855
|
+
if (config->wrapperSuppliedByThirdParty) {
|
856
|
+
e.setSolutionDescriptionHTML(
|
857
|
+
"<p class=\"sole-solution\">"
|
858
|
+
"This is a bug in the wrapper, so please contact the author of"
|
859
|
+
" the wrapper. This problem is outside " SHORT_PROGRAM_NAME
|
860
|
+
"'s control. Below follows the command that "
|
861
|
+
SHORT_PROGRAM_NAME " tried to execute, so that you can infer"
|
862
|
+
" which wrapper was used:</p>"
|
863
|
+
"<pre>" + escapeHTML(config->startCommand) + "</pre>");
|
864
|
+
} else {
|
865
|
+
e.setSolutionDescriptionHTML(
|
866
|
+
"<p class=\"sole-solution\">"
|
867
|
+
"This is a bug in " SHORT_PROGRAM_NAME "."
|
868
|
+
" <a href=\"" SUPPORT_URL "\">Please report this bug</a>"
|
869
|
+
" to the " SHORT_PROGRAM_NAME " authors.</p>");
|
870
|
+
}
|
871
|
+
|
872
|
+
throw e.finalize();
|
873
|
+
|
874
|
+
} else {
|
875
|
+
UPDATE_TRACE_POINT();
|
876
|
+
loadJourneyStateFromResponseDir();
|
877
|
+
session.journey.setStepErrored(SUBPROCESS_APP_LOAD_OR_EXEC, true);
|
878
|
+
|
879
|
+
SpawnException e(INTERNAL_ERROR, session.journey, config);
|
880
|
+
e.setSummary("Error spawning the web application:"
|
881
|
+
" the application's spawn response is invalid: "
|
882
|
+
+ toString(appSuppliedFieldErrors));
|
883
|
+
e.setAdvancedProblemDetails(toString(appSuppliedFieldErrors));
|
884
|
+
e.setSubprocessPid(pid);
|
885
|
+
e.setStdoutAndErrData(getStdoutErrData());
|
886
|
+
loadAnnotationsFromEnvDumpDir(e);
|
887
|
+
|
888
|
+
message = "<p>The " PROGRAM_NAME " application server tried"
|
889
|
+
" to start the web application, but encountered a bug"
|
890
|
+
" in the application. " SHORT_PROGRAM_NAME " expected"
|
891
|
+
" the application to communicate back various information"
|
892
|
+
" about its startup procedure, but the application"
|
893
|
+
" did not communicate back that correctly."
|
894
|
+
" The errors are as follows:</p>"
|
895
|
+
"<ul>";
|
896
|
+
end = appSuppliedFieldErrors.end();
|
897
|
+
for (it = appSuppliedFieldErrors.begin(); it != end; it++) {
|
898
|
+
message.append("<li>" + escapeHTML(*it) + "</li>");
|
899
|
+
}
|
900
|
+
message.append("</ul>");
|
901
|
+
e.setProblemDescriptionHTML(message);
|
902
|
+
|
903
|
+
if (config->genericApp) {
|
904
|
+
e.setSolutionDescriptionHTML(
|
905
|
+
"<p class=\"sole-solution\">"
|
906
|
+
"Since this is a bug in the web application, please "
|
907
|
+
"report this problem to the application's developer. "
|
908
|
+
"This problem is outside " SHORT_PROGRAM_NAME "'s "
|
909
|
+
"control.</p>");
|
910
|
+
} else {
|
911
|
+
e.setSolutionDescriptionHTML(
|
912
|
+
"<p class=\"sole-solution\">"
|
913
|
+
"This is a bug in " SHORT_PROGRAM_NAME "."
|
914
|
+
" <a href=\"" SUPPORT_URL "\">Please report this bug</a>"
|
915
|
+
" to the " SHORT_PROGRAM_NAME " authors.</p>");
|
916
|
+
}
|
917
|
+
|
918
|
+
throw e.finalize();
|
919
|
+
}
|
920
|
+
}
|
921
|
+
|
922
|
+
ErrorCategory inferErrorCategoryFromResponseDir(ErrorCategory defaultValue) const {
|
923
|
+
TRACE_POINT();
|
924
|
+
if (fileExists(session.responseDir + "/error/category")) {
|
925
|
+
string value = strip(readAll(session.responseDir + "/error/category"));
|
926
|
+
ErrorCategory category = stringToErrorCategory(value);
|
927
|
+
|
928
|
+
if (category == UNKNOWN_ERROR_CATEGORY) {
|
929
|
+
SpawnException e(INTERNAL_ERROR, session.journey, config);
|
930
|
+
e.setStdoutAndErrData(getStdoutErrData());
|
931
|
+
e.setSubprocessPid(pid);
|
932
|
+
loadAnnotationsFromEnvDumpDir(e);
|
933
|
+
|
934
|
+
if (!config->genericApp && config->startsUsingWrapper) {
|
935
|
+
if (config->wrapperSuppliedByThirdParty) {
|
936
|
+
e.setSummary(
|
937
|
+
"An error occurred while spawning an application process: "
|
938
|
+
"the application wrapper (which is not part of "
|
939
|
+
SHORT_PROGRAM_NAME
|
940
|
+
") reported an invalid error category: "
|
941
|
+
+ value);
|
942
|
+
} else {
|
943
|
+
e.setSummary(
|
944
|
+
"An error occurred while spawning an application process: "
|
945
|
+
"the application wrapper (which is internal to "
|
946
|
+
SHORT_PROGRAM_NAME
|
947
|
+
") reported an invalid error category: "
|
948
|
+
+ value);
|
949
|
+
}
|
950
|
+
} else {
|
951
|
+
e.setSummary(
|
952
|
+
"An error occurred while spawning an application process: "
|
953
|
+
"the application reported an invalid error category: "
|
954
|
+
+ value);
|
955
|
+
}
|
956
|
+
|
957
|
+
if (!config->genericApp && config->startsUsingWrapper) {
|
958
|
+
if (config->wrapperSuppliedByThirdParty) {
|
959
|
+
e.setProblemDescriptionHTML(
|
960
|
+
"<p>The " PROGRAM_NAME " application server tried"
|
961
|
+
" to start the web application through a"
|
962
|
+
" helper tool called the \"wrapper\". This helper tool "
|
963
|
+
" is not part of " SHORT_PROGRAM_NAME ". The tool "
|
964
|
+
" encountered an error, so " SHORT_PROGRAM_NAME
|
965
|
+
" expected the tool to report details about that error."
|
966
|
+
" But the tool communicated back in an invalid format:</p>"
|
967
|
+
"<ul>"
|
968
|
+
"<li>In file: " + escapeHTML(session.responseDir) + "/error/category</li>"
|
969
|
+
"<li>Content: <code>" + escapeHTML(value) + "</code></li>"
|
970
|
+
"</ul>");
|
971
|
+
e.setSolutionDescriptionHTML(
|
972
|
+
"<p class=\"sole-solution\">"
|
973
|
+
"This is a bug in the wrapper, so please contact the author of"
|
974
|
+
" the wrapper. This problem is outside " SHORT_PROGRAM_NAME
|
975
|
+
"'s control. Below follows the command that "
|
976
|
+
SHORT_PROGRAM_NAME " tried to execute, so that you can infer"
|
977
|
+
" which wrapper was used:</p>"
|
978
|
+
"<pre>" + escapeHTML(config->startCommand) + "</pre>");
|
979
|
+
} else {
|
980
|
+
e.setProblemDescriptionHTML(
|
981
|
+
"<p>The " PROGRAM_NAME " application server tried"
|
982
|
+
" to start the web application through a " SHORT_PROGRAM_NAME
|
983
|
+
"-internal helper tool called the \"wrapper\"."
|
984
|
+
" The tool encountered an error, so "
|
985
|
+
SHORT_PROGRAM_NAME " expected the tool to report"
|
986
|
+
" details about that error. But the tool communicated back"
|
987
|
+
" in an invalid format:</p>"
|
988
|
+
"<ul>"
|
989
|
+
"<li>In file: " + escapeHTML(session.responseDir) + "/error/category</li>"
|
990
|
+
"<li>Content: <code>" + escapeHTML(value) + "</code></li>"
|
991
|
+
"</ul>");
|
992
|
+
e.setSolutionDescriptionHTML(
|
993
|
+
"<p class=\"sole-solution\">"
|
994
|
+
"This is a bug in " SHORT_PROGRAM_NAME "."
|
995
|
+
" <a href=\"" SUPPORT_URL "\">Please report this bug</a>"
|
996
|
+
" to the " SHORT_PROGRAM_NAME " authors.</p>");
|
997
|
+
}
|
998
|
+
} else {
|
999
|
+
e.setProblemDescriptionHTML(
|
1000
|
+
"<p>The " PROGRAM_NAME " application server tried"
|
1001
|
+
" to start the web application. The application encountered "
|
1002
|
+
" an error and tried to report details about the error back to "
|
1003
|
+
SHORT_PROGRAM_NAME ". But the application communicated back"
|
1004
|
+
" in an invalid format:</p>"
|
1005
|
+
"<ul>"
|
1006
|
+
"<li>In file: " + escapeHTML(session.responseDir) + "/error/category</li>"
|
1007
|
+
"<li>Content: <code>" + escapeHTML(value) + "</code></li>"
|
1008
|
+
"</ul>");
|
1009
|
+
e.setSolutionDescriptionHTML(
|
1010
|
+
"<p class=\"sole-solution\">"
|
1011
|
+
"This is a bug in the web application, please "
|
1012
|
+
"report this problem to the application's developer. "
|
1013
|
+
"This problem is outside " SHORT_PROGRAM_NAME "'s "
|
1014
|
+
"control.</p>");
|
1015
|
+
}
|
1016
|
+
|
1017
|
+
throw e.finalize();
|
1018
|
+
} else {
|
1019
|
+
return category;
|
1020
|
+
}
|
1021
|
+
} else {
|
1022
|
+
return defaultValue;
|
1023
|
+
}
|
1024
|
+
}
|
1025
|
+
|
1026
|
+
void loadJourneyStateFromResponseDir() {
|
1027
|
+
loadJourneyStateFromResponseDir(session, pid, stdoutAndErrCapturer);
|
1028
|
+
}
|
1029
|
+
|
1030
|
+
static void loadJourneyStateFromResponseDir(HandshakeSession &session, pid_t pid,
|
1031
|
+
const BackgroundIOCapturerPtr &stdoutAndErrCapturer,
|
1032
|
+
JourneyStep firstStep, JourneyStep lastStep)
|
1033
|
+
{
|
1034
|
+
TRACE_POINT();
|
1035
|
+
JourneyStep step;
|
1036
|
+
|
1037
|
+
for (step = firstStep; step < lastStep; step = JourneyStep((int) step + 1)) {
|
1038
|
+
if (!session.journey.hasStep(step)) {
|
1039
|
+
continue;
|
1040
|
+
}
|
1041
|
+
|
1042
|
+
string stepString = journeyStepToStringLowerCase(step);
|
1043
|
+
string stepDir = session.responseDir + "/steps/" + stepString;
|
1044
|
+
if (!fileExists(stepDir + "/state")) {
|
1045
|
+
P_DEBUG("[App " << pid << " journey] Step " << journeyStepToString(step)
|
1046
|
+
<< ": state file does not exist");
|
1047
|
+
continue;
|
1048
|
+
}
|
1049
|
+
|
1050
|
+
loadJourneyStateFromResponseDirForSpecificStep(
|
1051
|
+
session, pid, stdoutAndErrCapturer, step, stepDir);
|
1052
|
+
}
|
1053
|
+
}
|
1054
|
+
|
1055
|
+
static void loadJourneyStateFromResponseDirForSpecificStep(HandshakeSession &session,
|
1056
|
+
pid_t pid, const BackgroundIOCapturerPtr &stdoutAndErrCapturer,
|
1057
|
+
JourneyStep step, const string &stepDir)
|
1058
|
+
{
|
1059
|
+
TRACE_POINT_WITH_DATA(journeyStepToString(step).data());
|
1060
|
+
string summary;
|
1061
|
+
string value = strip(readAll(stepDir + "/state"));
|
1062
|
+
JourneyStepState state = stringToJourneyStepState(value);
|
1063
|
+
const Config *config = session.config;
|
1064
|
+
|
1065
|
+
if (value.empty()) {
|
1066
|
+
P_DEBUG("[App " << pid << " journey] Step " << journeyStepToString(step)
|
1067
|
+
<< ": state file is empty");
|
1068
|
+
return;
|
1069
|
+
}
|
1070
|
+
|
1071
|
+
P_DEBUG("[App " << pid << " journey] Step " << journeyStepToString(step)
|
1072
|
+
<< ": setting state to " << value);
|
1073
|
+
|
1074
|
+
try {
|
1075
|
+
UPDATE_TRACE_POINT();
|
1076
|
+
switch (state) {
|
1077
|
+
case STEP_NOT_STARTED:
|
1078
|
+
// SpawnEnvSetupper explicitly sets the SUBPROCESS_OS_SHELL
|
1079
|
+
// step state to STEP_NOT_STARTED if it determines that it
|
1080
|
+
// should not execute the next command through the shell.
|
1081
|
+
session.journey.setStepNotStarted(step, true);
|
1082
|
+
break;
|
1083
|
+
case STEP_IN_PROGRESS:
|
1084
|
+
session.journey.setStepInProgress(step, true);
|
1085
|
+
break;
|
1086
|
+
case STEP_PERFORMED:
|
1087
|
+
session.journey.setStepPerformed(step, true);
|
1088
|
+
break;
|
1089
|
+
case STEP_ERRORED:
|
1090
|
+
session.journey.setStepErrored(step, true);
|
1091
|
+
break;
|
1092
|
+
default:
|
1093
|
+
session.journey.setStepErrored(step, true);
|
1094
|
+
|
1095
|
+
SpawnException e(INTERNAL_ERROR, session.journey, config);
|
1096
|
+
e.setStdoutAndErrData(getStdoutErrData(stdoutAndErrCapturer));
|
1097
|
+
e.setSubprocessPid(pid);
|
1098
|
+
loadAnnotationsFromEnvDumpDir(e, session);
|
1099
|
+
|
1100
|
+
if (!config->genericApp && config->startsUsingWrapper) {
|
1101
|
+
if (config->wrapperSuppliedByThirdParty) {
|
1102
|
+
e.setSummary(
|
1103
|
+
"An error occurred while spawning an application process: "
|
1104
|
+
"the application wrapper (which is not part of " SHORT_PROGRAM_NAME
|
1105
|
+
") reported an invalid progress step state for step "
|
1106
|
+
+ journeyStepToString(step) + ": " + value);
|
1107
|
+
} else {
|
1108
|
+
e.setSummary(
|
1109
|
+
"An error occurred while spawning an application process: "
|
1110
|
+
"the application wrapper (which is internal to " SHORT_PROGRAM_NAME
|
1111
|
+
") reported an invalid progress step state for step "
|
1112
|
+
+ journeyStepToString(step) + ": " + value);
|
1113
|
+
}
|
1114
|
+
} else {
|
1115
|
+
e.setSummary(
|
1116
|
+
"An error occurred while spawning an application process: "
|
1117
|
+
"the application reported an invalid progress step state for step "
|
1118
|
+
+ journeyStepToString(step) + ": " + value);
|
1119
|
+
}
|
1120
|
+
|
1121
|
+
if (!config->genericApp && config->startsUsingWrapper) {
|
1122
|
+
if (config->wrapperSuppliedByThirdParty) {
|
1123
|
+
e.setProblemDescriptionHTML(
|
1124
|
+
"<p>The " PROGRAM_NAME " application server tried"
|
1125
|
+
" to start the web application through a"
|
1126
|
+
" helper tool called the \"wrapper\". This helper tool"
|
1127
|
+
" is not part of " SHORT_PROGRAM_NAME ". "
|
1128
|
+
SHORT_PROGRAM_NAME " expected the helper tool to"
|
1129
|
+
" report about its startup progress, but the tool"
|
1130
|
+
" communicated back an invalid answer:</p>"
|
1131
|
+
"<ul>"
|
1132
|
+
"<li>In file: " + escapeHTML(stepDir) + "/state</li>"
|
1133
|
+
"<li>Content: <code>" + escapeHTML(value) + "</code></li>"
|
1134
|
+
"</ul>");
|
1135
|
+
e.setSolutionDescriptionHTML(
|
1136
|
+
"<p class=\"sole-solution\">"
|
1137
|
+
"This is a bug in the wrapper, so please contact the author of"
|
1138
|
+
" the wrapper. This problem is outside " SHORT_PROGRAM_NAME
|
1139
|
+
"'s control. Below follows the command that "
|
1140
|
+
SHORT_PROGRAM_NAME " tried to execute, so that you can infer"
|
1141
|
+
" which wrapper was used:</p>"
|
1142
|
+
"<pre>" + escapeHTML(config->startCommand) + "</pre>");
|
1143
|
+
} else {
|
1144
|
+
e.setProblemDescriptionHTML(
|
1145
|
+
"<p>The " PROGRAM_NAME " application server tried"
|
1146
|
+
" to start the web application through a " SHORT_PROGRAM_NAME
|
1147
|
+
"-internal helper tool called the \"wrapper\","
|
1148
|
+
" but " SHORT_PROGRAM_NAME " encountered a bug"
|
1149
|
+
" in this helper tool. " SHORT_PROGRAM_NAME " expected"
|
1150
|
+
" the helper tool to report about its startup progress,"
|
1151
|
+
" but the tool communicated back an invalid answer:</p>"
|
1152
|
+
"<ul>"
|
1153
|
+
"<li>In file: " + escapeHTML(stepDir) + "/state</li>"
|
1154
|
+
"<li>Content: <code>" + escapeHTML(value) + "</code></li>"
|
1155
|
+
"</ul>");
|
1156
|
+
e.setSolutionDescriptionHTML(
|
1157
|
+
"<p class=\"sole-solution\">"
|
1158
|
+
"This is a bug in " SHORT_PROGRAM_NAME "."
|
1159
|
+
" <a href=\"" SUPPORT_URL "\">Please report this bug</a>"
|
1160
|
+
" to the " SHORT_PROGRAM_NAME " authors.</p>");
|
1161
|
+
}
|
1162
|
+
} else {
|
1163
|
+
e.setProblemDescriptionHTML(
|
1164
|
+
"<p>The " PROGRAM_NAME " application server tried"
|
1165
|
+
" to start the web application, and expected the application"
|
1166
|
+
" to report about its startup progress. But the application"
|
1167
|
+
" communicated back an invalid answer:</p>"
|
1168
|
+
"<ul>"
|
1169
|
+
"<li>In file: " + escapeHTML(stepDir) + "/state</li>"
|
1170
|
+
"<li>Content: <code>" + escapeHTML(value) + "</code></li>"
|
1171
|
+
"</ul>");
|
1172
|
+
e.setSolutionDescriptionHTML(
|
1173
|
+
"<p class=\"sole-solution\">"
|
1174
|
+
"This is a bug in the web application, please "
|
1175
|
+
"report this problem to the application's developer. "
|
1176
|
+
"This problem is outside " SHORT_PROGRAM_NAME "'s "
|
1177
|
+
"control.</p>");
|
1178
|
+
}
|
1179
|
+
|
1180
|
+
throw e.finalize();
|
1181
|
+
break;
|
1182
|
+
};
|
1183
|
+
} catch (const RuntimeException &originalException) {
|
1184
|
+
UPDATE_TRACE_POINT();
|
1185
|
+
session.journey.setStepErrored(step, true);
|
1186
|
+
|
1187
|
+
SpawnException e(INTERNAL_ERROR, session.journey, config);
|
1188
|
+
e.setStdoutAndErrData(getStdoutErrData(stdoutAndErrCapturer));
|
1189
|
+
e.setSubprocessPid(pid);
|
1190
|
+
loadAnnotationsFromEnvDumpDir(e, session);
|
1191
|
+
|
1192
|
+
if (!config->genericApp && config->startsUsingWrapper) {
|
1193
|
+
if (config->wrapperSuppliedByThirdParty) {
|
1194
|
+
e.setSummary("An error occurred while spawning an application process: "
|
1195
|
+
"the application wrapper (which is not part of " SHORT_PROGRAM_NAME
|
1196
|
+
") reported an invalid progress step state for step "
|
1197
|
+
+ journeyStepToString(step) + ": "
|
1198
|
+
+ StaticString(originalException.what()));
|
1199
|
+
} else {
|
1200
|
+
e.setSummary("An error occurred while spawning an application process: "
|
1201
|
+
"the application wrapper (which is internal to " SHORT_PROGRAM_NAME
|
1202
|
+
") reported an invalid progress step state for step "
|
1203
|
+
+ journeyStepToString(step) + ": "
|
1204
|
+
+ StaticString(originalException.what()));
|
1205
|
+
}
|
1206
|
+
} else {
|
1207
|
+
e.setSummary("An error occurred while spawning an application process: "
|
1208
|
+
"the application reported an invalid progress step state for step "
|
1209
|
+
+ journeyStepToString(step) + ": "
|
1210
|
+
+ StaticString(originalException.what()));
|
1211
|
+
}
|
1212
|
+
|
1213
|
+
if (!config->genericApp && config->startsUsingWrapper) {
|
1214
|
+
if (config->wrapperSuppliedByThirdParty) {
|
1215
|
+
e.setProblemDescriptionHTML(
|
1216
|
+
"<p>The " PROGRAM_NAME " application server tried"
|
1217
|
+
" to start the web application through a "
|
1218
|
+
" helper tool called the \"wrapper\". This helper tool"
|
1219
|
+
" is not part of " SHORT_PROGRAM_NAME ". "
|
1220
|
+
SHORT_PROGRAM_NAME " expected the helper tool to"
|
1221
|
+
" report about its startup progress, but the tool"
|
1222
|
+
" communicated back an invalid answer:</p>"
|
1223
|
+
"<ul>"
|
1224
|
+
"<li>In file: " + escapeHTML(stepDir) + "/state</li>"
|
1225
|
+
"<li>Error: " + escapeHTML(originalException.what()) + "</li>"
|
1226
|
+
"</ul>");
|
1227
|
+
e.setSolutionDescriptionHTML(
|
1228
|
+
"<p class=\"sole-solution\">"
|
1229
|
+
"This is a bug in the wrapper, so please contact the author of"
|
1230
|
+
" the wrapper. This problem is outside " SHORT_PROGRAM_NAME
|
1231
|
+
"'s control. Below follows the command that "
|
1232
|
+
SHORT_PROGRAM_NAME " tried to execute, so that you can infer"
|
1233
|
+
" which wrapper was used:</p>"
|
1234
|
+
"<pre>" + escapeHTML(config->startCommand) + "</pre>");
|
1235
|
+
} else {
|
1236
|
+
e.setProblemDescriptionHTML(
|
1237
|
+
"<p>The " PROGRAM_NAME " application server tried"
|
1238
|
+
" to start the web application through a " SHORT_PROGRAM_NAME
|
1239
|
+
"-internal helper tool called the \"wrapper\","
|
1240
|
+
" but " SHORT_PROGRAM_NAME " encountered a bug"
|
1241
|
+
" in this helper tool. " SHORT_PROGRAM_NAME " expected"
|
1242
|
+
" the helper tool to report about its startup progress,"
|
1243
|
+
" but the tool communicated back an invalid answer:</p>"
|
1244
|
+
"<ul>"
|
1245
|
+
"<li>In file: " + escapeHTML(stepDir) + "/state</li>"
|
1246
|
+
"<li>Error: " + escapeHTML(originalException.what()) + "</li>"
|
1247
|
+
"</ul>");
|
1248
|
+
e.setSolutionDescriptionHTML(
|
1249
|
+
"<p class=\"sole-solution\">"
|
1250
|
+
"This is a bug in " SHORT_PROGRAM_NAME "."
|
1251
|
+
" <a href=\"" SUPPORT_URL "\">Please report this bug</a>"
|
1252
|
+
" to the " SHORT_PROGRAM_NAME " authors.</p>");
|
1253
|
+
}
|
1254
|
+
} else {
|
1255
|
+
e.setProblemDescriptionHTML(
|
1256
|
+
"<p>The " PROGRAM_NAME " application server tried"
|
1257
|
+
" to start the web application, and expected the application"
|
1258
|
+
" to report about its startup progress. But the application"
|
1259
|
+
" communicated back an invalid answer:</p>"
|
1260
|
+
"<ul>"
|
1261
|
+
"<li>In file: " + escapeHTML(stepDir) + "/state</li>"
|
1262
|
+
"<li>Error: " + escapeHTML(originalException.what()) + "</li>"
|
1263
|
+
"</ul>");
|
1264
|
+
e.setSolutionDescriptionHTML(
|
1265
|
+
"<p class=\"sole-solution\">"
|
1266
|
+
"This is a bug in the web application, please "
|
1267
|
+
"report this problem to the application's developer. "
|
1268
|
+
"This problem is outside " SHORT_PROGRAM_NAME "'s "
|
1269
|
+
"control.</p>");
|
1270
|
+
}
|
1271
|
+
|
1272
|
+
throw e.finalize();
|
1273
|
+
}
|
1274
|
+
|
1275
|
+
UPDATE_TRACE_POINT();
|
1276
|
+
if (fileExists(stepDir + "/begin_time_monotonic")) {
|
1277
|
+
value = readAll(stepDir + "/begin_time_monotonic");
|
1278
|
+
MonotonicTimeUsec beginTimeMonotonic = atof(value.c_str()) * 1000000;
|
1279
|
+
P_DEBUG("[App " << pid << " journey] Step " << journeyStepToString(step)
|
1280
|
+
<< ": monotonic begin time is \"" << cEscapeString(value) << "\"");
|
1281
|
+
session.journey.setStepBeginTime(step, beginTimeMonotonic);
|
1282
|
+
} else if (fileExists(stepDir + "/begin_time")) {
|
1283
|
+
value = readAll(stepDir + "/begin_time");
|
1284
|
+
unsigned long long beginTime = atof(value.c_str()) * 1000000;
|
1285
|
+
MonotonicTimeUsec beginTimeMonotonic = usecTimestampToMonoTime(beginTime);
|
1286
|
+
P_DEBUG("[App " << pid << " journey] Step " << journeyStepToString(step)
|
1287
|
+
<< ": begin time is \"" << cEscapeString(value) << "\", monotonic conversion is "
|
1288
|
+
<< doubleToString(beginTimeMonotonic / 1000000.0));
|
1289
|
+
session.journey.setStepBeginTime(step, beginTimeMonotonic);
|
1290
|
+
} else {
|
1291
|
+
P_DEBUG("[App " << pid << " journey] Step " << journeyStepToString(step)
|
1292
|
+
<< ": no begin time known");
|
1293
|
+
}
|
1294
|
+
|
1295
|
+
UPDATE_TRACE_POINT();
|
1296
|
+
if (fileExists(stepDir + "/end_time_monotonic")) {
|
1297
|
+
value = readAll(stepDir + "/end_time_monotonic");
|
1298
|
+
MonotonicTimeUsec endTimeMonotonic = atof(value.c_str()) * 1000000;
|
1299
|
+
P_DEBUG("[App " << pid << " journey] Step " << journeyStepToString(step)
|
1300
|
+
<< ": monotonic end time is \"" << cEscapeString(value) << "\"");
|
1301
|
+
session.journey.setStepEndTime(step, endTimeMonotonic);
|
1302
|
+
} else if (fileExists(stepDir + "/end_time")) {
|
1303
|
+
value = readAll(stepDir + "/end_time");
|
1304
|
+
unsigned long long endTime = atof(value.c_str()) * 1000000;
|
1305
|
+
MonotonicTimeUsec endTimeMonotonic = usecTimestampToMonoTime(endTime);
|
1306
|
+
P_DEBUG("[App " << pid << " journey] Step " << journeyStepToString(step)
|
1307
|
+
<< ": end time is \"" << cEscapeString(value) << "\", monotonic conversion is "
|
1308
|
+
<< doubleToString(endTimeMonotonic / 1000000.0));
|
1309
|
+
session.journey.setStepEndTime(step, endTimeMonotonic);
|
1310
|
+
} else {
|
1311
|
+
P_DEBUG("[App " << pid << " journey] Step " << journeyStepToString(step)
|
1312
|
+
<< ": no end time known");
|
1313
|
+
}
|
1314
|
+
}
|
1315
|
+
|
1316
|
+
static MonotonicTimeUsec usecTimestampToMonoTime(unsigned long long timestamp) {
|
1317
|
+
unsigned long long now = SystemTime::getUsec();
|
1318
|
+
MonotonicTimeUsec nowMono = SystemTime::getMonotonicUsec();
|
1319
|
+
unsigned long long diff;
|
1320
|
+
|
1321
|
+
if (now > nowMono) {
|
1322
|
+
diff = now - nowMono;
|
1323
|
+
return timestamp - diff;
|
1324
|
+
} else {
|
1325
|
+
diff = nowMono - now;
|
1326
|
+
return timestamp + diff;
|
1327
|
+
}
|
1328
|
+
}
|
1329
|
+
|
1330
|
+
void loadSubprocessErrorMessagesAndEnvDump(SpawnException &e) const {
|
1331
|
+
TRACE_POINT();
|
1332
|
+
const string &responseDir = session.responseDir;
|
1333
|
+
const string &envDumpDir = session.envDumpDir;
|
1334
|
+
|
1335
|
+
if (fileExists(responseDir + "/error/summary")) {
|
1336
|
+
e.setSummary(strip(readAll(responseDir + "/error/summary")));
|
1337
|
+
}
|
1338
|
+
|
1339
|
+
if (e.getAdvancedProblemDetails().empty()
|
1340
|
+
&& fileExists(responseDir + "/error/advanced_problem_details"))
|
1341
|
+
{
|
1342
|
+
e.setAdvancedProblemDetails(strip(readAll(responseDir
|
1343
|
+
+ "/error/advanced_problem_details")));
|
1344
|
+
}
|
1345
|
+
|
1346
|
+
if (fileExists(responseDir + "/error/problem_description.html")) {
|
1347
|
+
e.setProblemDescriptionHTML(readAll(responseDir + "/error/problem_description.html"));
|
1348
|
+
} else if (fileExists(responseDir + "/error/problem_description.txt")) {
|
1349
|
+
e.setProblemDescriptionHTML(escapeHTML(strip(readAll(
|
1350
|
+
responseDir + "/error/problem_description.txt"))));
|
1351
|
+
}
|
1352
|
+
|
1353
|
+
if (fileExists(responseDir + "/error/solution_description.html")) {
|
1354
|
+
e.setSolutionDescriptionHTML(readAll(responseDir + "/error/solution_description.html"));
|
1355
|
+
} else if (fileExists(responseDir + "/error/solution_description.txt")) {
|
1356
|
+
e.setSolutionDescriptionHTML(escapeHTML(strip(readAll(
|
1357
|
+
responseDir + "/error/solution_description.txt"))));
|
1358
|
+
}
|
1359
|
+
|
1360
|
+
string envvars, userInfo, ulimits;
|
1361
|
+
loadBasicInfoFromEnvDumpDir(envDumpDir, envvars, userInfo, ulimits);
|
1362
|
+
e.setSubprocessEnvvars(envvars);
|
1363
|
+
e.setSubprocessUserInfo(userInfo);
|
1364
|
+
e.setSubprocessUlimits(ulimits);
|
1365
|
+
|
1366
|
+
loadAnnotationsFromEnvDumpDir(e);
|
1367
|
+
}
|
1368
|
+
|
1369
|
+
static void doClosedir(DIR *dir) {
|
1370
|
+
closedir(dir);
|
1371
|
+
}
|
1372
|
+
|
1373
|
+
void loadAnnotationsFromEnvDumpDir(SpawnException &e) const {
|
1374
|
+
loadAnnotationsFromEnvDumpDir(e, session);
|
1375
|
+
}
|
1376
|
+
|
1377
|
+
static void loadAnnotationsFromEnvDumpDir(SpawnException &e, HandshakeSession &session) {
|
1378
|
+
TRACE_POINT();
|
1379
|
+
string path = session.envDumpDir + "/annotations";
|
1380
|
+
DIR *dir = opendir(path.c_str());
|
1381
|
+
if (dir == NULL) {
|
1382
|
+
return;
|
1383
|
+
}
|
1384
|
+
|
1385
|
+
ScopeGuard guard(boost::bind(doClosedir, dir));
|
1386
|
+
struct dirent *ent;
|
1387
|
+
while ((ent = readdir(dir)) != NULL) {
|
1388
|
+
if (ent->d_name[0] != '.') {
|
1389
|
+
e.setAnnotation(ent->d_name, strip(
|
1390
|
+
Passenger::readAll(path + "/" + ent->d_name)));
|
1391
|
+
}
|
1392
|
+
}
|
1393
|
+
}
|
1394
|
+
|
1395
|
+
void cleanup() {
|
1396
|
+
boost::this_thread::disable_interruption di;
|
1397
|
+
boost::this_thread::disable_syscall_interruption dsi;
|
1398
|
+
TRACE_POINT();
|
1399
|
+
|
1400
|
+
if (processExitWatcher != NULL) {
|
1401
|
+
processExitWatcher->interrupt_and_join();
|
1402
|
+
delete processExitWatcher;
|
1403
|
+
processExitWatcher = NULL;
|
1404
|
+
}
|
1405
|
+
if (finishSignalWatcher != NULL) {
|
1406
|
+
finishSignalWatcher->interrupt_and_join();
|
1407
|
+
delete finishSignalWatcher;
|
1408
|
+
finishSignalWatcher = NULL;
|
1409
|
+
}
|
1410
|
+
if (socketPingabilityWatcher != NULL) {
|
1411
|
+
socketPingabilityWatcher->interrupt_and_join();
|
1412
|
+
delete socketPingabilityWatcher;
|
1413
|
+
socketPingabilityWatcher = NULL;
|
1414
|
+
}
|
1415
|
+
if (stdoutAndErrCapturer != NULL) {
|
1416
|
+
stdoutAndErrCapturer->stop();
|
1417
|
+
}
|
1418
|
+
}
|
1419
|
+
|
1420
|
+
JourneyStep bestGuessSubprocessFailedStep() const {
|
1421
|
+
JourneyStep step = getFirstSubprocessJourneyStepWithState(STEP_IN_PROGRESS);
|
1422
|
+
if (step != UNKNOWN_JOURNEY_STEP) {
|
1423
|
+
return step;
|
1424
|
+
}
|
1425
|
+
|
1426
|
+
if (allSubprocessJourneyStepsHaveState(STEP_PERFORMED)) {
|
1427
|
+
return getLastSubprocessJourneyStepFrom(session.journey);
|
1428
|
+
} else {
|
1429
|
+
JourneyStep step = getLastSubprocessJourneyStepWithState(STEP_PERFORMED);
|
1430
|
+
if (step == UNKNOWN_JOURNEY_STEP) {
|
1431
|
+
return getFirstSubprocessJourneyStepFrom(session.journey);
|
1432
|
+
} else {
|
1433
|
+
assert(step != getLastSubprocessJourneyStepFrom(session.journey));
|
1434
|
+
return JourneyStep((int) step + 1);
|
1435
|
+
}
|
1436
|
+
}
|
1437
|
+
}
|
1438
|
+
|
1439
|
+
JourneyStep getFirstSubprocessJourneyStepFrom(const Journey &journey) const {
|
1440
|
+
JourneyStep firstStep = getFirstSubprocessJourneyStep();
|
1441
|
+
JourneyStep lastStep = getLastSubprocessJourneyStep();
|
1442
|
+
JourneyStep step;
|
1443
|
+
|
1444
|
+
for (step = firstStep; step <= lastStep; step = JourneyStep((int) step + 1)) {
|
1445
|
+
if (session.journey.hasStep(step)) {
|
1446
|
+
return step;
|
1447
|
+
}
|
1448
|
+
}
|
1449
|
+
|
1450
|
+
P_BUG("Never reached");
|
1451
|
+
return UNKNOWN_JOURNEY_STEP;
|
1452
|
+
}
|
1453
|
+
|
1454
|
+
JourneyStep getLastSubprocessJourneyStepFrom(const Journey &journey) const {
|
1455
|
+
JourneyStep firstStep = getFirstSubprocessJourneyStep();
|
1456
|
+
JourneyStep lastStep = getLastSubprocessJourneyStep();
|
1457
|
+
JourneyStep result = UNKNOWN_JOURNEY_STEP;
|
1458
|
+
JourneyStep step;
|
1459
|
+
|
1460
|
+
for (step = firstStep; step <= lastStep; step = JourneyStep((int) step + 1)) {
|
1461
|
+
if (session.journey.hasStep(step)) {
|
1462
|
+
result = step;
|
1463
|
+
}
|
1464
|
+
}
|
1465
|
+
|
1466
|
+
return result;
|
1467
|
+
}
|
1468
|
+
|
1469
|
+
bool allSubprocessJourneyStepsHaveState(JourneyStepState state) const {
|
1470
|
+
JourneyStep firstStep = getFirstSubprocessJourneyStep();
|
1471
|
+
JourneyStep lastStep = getLastSubprocessJourneyStep();
|
1472
|
+
JourneyStep step;
|
1473
|
+
|
1474
|
+
for (step = firstStep; step <= lastStep; step = JourneyStep((int) step + 1)) {
|
1475
|
+
if (!session.journey.hasStep(step)) {
|
1476
|
+
continue;
|
1477
|
+
}
|
1478
|
+
|
1479
|
+
if (session.journey.getStepInfo(step).state != state) {
|
1480
|
+
return false;
|
1481
|
+
}
|
1482
|
+
}
|
1483
|
+
|
1484
|
+
return true;
|
1485
|
+
}
|
1486
|
+
|
1487
|
+
JourneyStep getFirstSubprocessJourneyStepWithState(JourneyStepState state) const {
|
1488
|
+
JourneyStep firstStep = getFirstSubprocessJourneyStep();
|
1489
|
+
JourneyStep lastStep = getLastSubprocessJourneyStep();
|
1490
|
+
JourneyStep step;
|
1491
|
+
|
1492
|
+
for (step = firstStep; step <= lastStep; step = JourneyStep((int) step + 1)) {
|
1493
|
+
if (!session.journey.hasStep(step)) {
|
1494
|
+
continue;
|
1495
|
+
}
|
1496
|
+
|
1497
|
+
if (session.journey.getStepInfo(step).state == state) {
|
1498
|
+
return step;
|
1499
|
+
}
|
1500
|
+
}
|
1501
|
+
|
1502
|
+
return UNKNOWN_JOURNEY_STEP;
|
1503
|
+
}
|
1504
|
+
|
1505
|
+
JourneyStep getLastSubprocessJourneyStepWithState(JourneyStepState state) const {
|
1506
|
+
JourneyStep firstStep = getFirstSubprocessJourneyStep();
|
1507
|
+
JourneyStep lastStep = getLastSubprocessJourneyStep();
|
1508
|
+
JourneyStep step;
|
1509
|
+
JourneyStep result = UNKNOWN_JOURNEY_STEP;
|
1510
|
+
|
1511
|
+
for (step = firstStep; step <= lastStep; step = JourneyStep((int) step + 1)) {
|
1512
|
+
if (!session.journey.hasStep(step)) {
|
1513
|
+
continue;
|
1514
|
+
}
|
1515
|
+
|
1516
|
+
if (session.journey.getStepInfo(step).state == state) {
|
1517
|
+
result = step;
|
1518
|
+
}
|
1519
|
+
}
|
1520
|
+
|
1521
|
+
return result;
|
1522
|
+
}
|
1523
|
+
|
1524
|
+
public:
|
1525
|
+
struct DebugSupport {
|
1526
|
+
virtual ~DebugSupport() { }
|
1527
|
+
virtual void beginWaitUntilSpawningFinished() { }
|
1528
|
+
};
|
1529
|
+
|
1530
|
+
DebugSupport *debugSupport;
|
1531
|
+
|
1532
|
+
|
1533
|
+
HandshakePerform(HandshakeSession &_session, pid_t _pid,
|
1534
|
+
const FileDescriptor &_stdinFd = FileDescriptor(),
|
1535
|
+
const FileDescriptor &_stdoutAndErrFd = FileDescriptor(),
|
1536
|
+
const string &_alreadyReadStdoutAndErrData = string())
|
1537
|
+
: session(_session),
|
1538
|
+
config(session.config),
|
1539
|
+
pid(_pid),
|
1540
|
+
stdinFd(_stdinFd),
|
1541
|
+
stdoutAndErrFd(_stdoutAndErrFd),
|
1542
|
+
alreadyReadStdoutAndErrData(_alreadyReadStdoutAndErrData),
|
1543
|
+
processExitWatcher(NULL),
|
1544
|
+
finishSignalWatcher(NULL),
|
1545
|
+
processExited(false),
|
1546
|
+
finishState(NOT_FINISHED),
|
1547
|
+
socketPingabilityWatcher(NULL),
|
1548
|
+
socketIsNowPingable(false),
|
1549
|
+
debugSupport(NULL)
|
1550
|
+
{
|
1551
|
+
assert(_session.context != NULL);
|
1552
|
+
assert(_session.context->isFinalized());
|
1553
|
+
assert(_session.config != NULL);
|
1554
|
+
}
|
1555
|
+
|
1556
|
+
Result execute() {
|
1557
|
+
TRACE_POINT();
|
1558
|
+
ScopeGuard guard(boost::bind(&HandshakePerform::cleanup, this));
|
1559
|
+
|
1560
|
+
// We do not set SPAWNING_KIT_HANDSHAKE_PERFORM to the IN_PROGRESS or
|
1561
|
+
// PERFORMED state here. That will be done by the caller because
|
1562
|
+
// it may want to perform additional preparation.
|
1563
|
+
|
1564
|
+
try {
|
1565
|
+
initializeStdchannelsCapturing();
|
1566
|
+
startWatchingProcessExit();
|
1567
|
+
if (config->genericApp || config->findFreePort) {
|
1568
|
+
startWatchingSocketPingability();
|
1569
|
+
}
|
1570
|
+
if (!config->genericApp) {
|
1571
|
+
startWatchingFinishSignal();
|
1572
|
+
}
|
1573
|
+
} catch (const SpawnException &) {
|
1574
|
+
throw;
|
1575
|
+
} catch (const std::exception &originalException) {
|
1576
|
+
sleepShortlyToCaptureMoreStdoutStderr();
|
1577
|
+
|
1578
|
+
loadJourneyStateFromResponseDir();
|
1579
|
+
session.journey.setStepErrored(SPAWNING_KIT_HANDSHAKE_PERFORM);
|
1580
|
+
|
1581
|
+
SpawnException e(originalException, session.journey, config);
|
1582
|
+
e.setStdoutAndErrData(getStdoutErrData());
|
1583
|
+
e.setSubprocessPid(pid);
|
1584
|
+
throw e.finalize();
|
1585
|
+
}
|
1586
|
+
|
1587
|
+
UPDATE_TRACE_POINT();
|
1588
|
+
try {
|
1589
|
+
boost::unique_lock<boost::mutex> l(syncher);
|
1590
|
+
if (debugSupport != NULL) {
|
1591
|
+
debugSupport->beginWaitUntilSpawningFinished();
|
1592
|
+
}
|
1593
|
+
waitUntilSpawningFinished(l);
|
1594
|
+
Result result = handleResponse();
|
1595
|
+
loadJourneyStateFromResponseDir();
|
1596
|
+
return result;
|
1597
|
+
} catch (const SpawnException &) {
|
1598
|
+
throw;
|
1599
|
+
} catch (const std::exception &originalException) {
|
1600
|
+
sleepShortlyToCaptureMoreStdoutStderr();
|
1601
|
+
|
1602
|
+
loadJourneyStateFromResponseDir();
|
1603
|
+
session.journey.setStepErrored(SPAWNING_KIT_HANDSHAKE_PERFORM);
|
1604
|
+
|
1605
|
+
SpawnException e(originalException, session.journey, config);
|
1606
|
+
e.setSubprocessPid(pid);
|
1607
|
+
e.setStdoutAndErrData(getStdoutErrData());
|
1608
|
+
throw e.finalize();
|
1609
|
+
}
|
1610
|
+
}
|
1611
|
+
|
1612
|
+
static void loadJourneyStateFromResponseDir(HandshakeSession &session, pid_t pid,
|
1613
|
+
const BackgroundIOCapturerPtr &stdoutAndErrCapturer)
|
1614
|
+
{
|
1615
|
+
TRACE_POINT();
|
1616
|
+
|
1617
|
+
P_DEBUG("[App " << pid << " journey] Loading state from " << session.responseDir);
|
1618
|
+
|
1619
|
+
loadJourneyStateFromResponseDir(session, pid, stdoutAndErrCapturer,
|
1620
|
+
getFirstSubprocessJourneyStep(),
|
1621
|
+
getLastSubprocessJourneyStep());
|
1622
|
+
|
1623
|
+
UPDATE_TRACE_POINT();
|
1624
|
+
loadJourneyStateFromResponseDir(session, pid, stdoutAndErrCapturer,
|
1625
|
+
getFirstPreloaderJourneyStep(),
|
1626
|
+
// Also load state from PRELOADER_FINISH since the
|
1627
|
+
// preloader writes there.
|
1628
|
+
JourneyStep((int) getLastPreloaderJourneyStep() + 1));
|
1629
|
+
}
|
1630
|
+
|
1631
|
+
static void loadBasicInfoFromEnvDumpDir(const string &envDumpDir,
|
1632
|
+
string &envvars, string &userInfo, string &ulimits)
|
1633
|
+
{
|
1634
|
+
if (fileExists(envDumpDir + "/envvars")) {
|
1635
|
+
envvars = readAll(envDumpDir + "/envvars");
|
1636
|
+
}
|
1637
|
+
if (fileExists(envDumpDir + "/user_info")) {
|
1638
|
+
userInfo = readAll(envDumpDir + "/user_info");
|
1639
|
+
}
|
1640
|
+
if (fileExists(envDumpDir + "/ulimits")) {
|
1641
|
+
ulimits = readAll(envDumpDir + "/ulimits");
|
1642
|
+
}
|
1643
|
+
}
|
1644
|
+
};
|
1645
|
+
|
1646
|
+
|
1647
|
+
} // namespace SpawningKit
|
1648
|
+
} // namespace Passenger
|
1649
|
+
|
1650
|
+
#endif /* _PASSENGER_SPAWNING_KIT_HANDSHAKE_PERFORM_H_ */
|