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,25 +26,200 @@
|
|
26
26
|
#ifndef _PASSENGER_SPAWNING_KIT_RESULT_H_
|
27
27
|
#define _PASSENGER_SPAWNING_KIT_RESULT_H_
|
28
28
|
|
29
|
+
#include <string>
|
30
|
+
#include <vector>
|
31
|
+
|
32
|
+
#include <sys/types.h>
|
33
|
+
|
29
34
|
#include <FileDescriptor.h>
|
30
|
-
#include <
|
35
|
+
#include <Exceptions.h>
|
36
|
+
#include <Utils/SystemTime.h>
|
37
|
+
#include <ConfigKit/ConfigKit.h>
|
38
|
+
#include <Core/SpawningKit/Context.h>
|
39
|
+
#include <Core/SpawningKit/Config.h>
|
31
40
|
|
32
41
|
namespace Passenger {
|
33
42
|
namespace SpawningKit {
|
34
43
|
|
44
|
+
using namespace std;
|
45
|
+
|
35
46
|
|
36
47
|
/**
|
37
|
-
* Represents the result of a spawning operation.
|
38
|
-
*
|
39
|
-
*
|
48
|
+
* Represents the result of a spawning operation.
|
49
|
+
*
|
50
|
+
* - begin hinted parseable class -
|
40
51
|
*/
|
41
|
-
|
42
|
-
|
43
|
-
|
52
|
+
class Result {
|
53
|
+
public:
|
54
|
+
struct Socket {
|
55
|
+
struct Schema: public ConfigKit::Schema {
|
56
|
+
Schema() {
|
57
|
+
using namespace Passenger::ConfigKit;
|
58
|
+
|
59
|
+
add("address", STRING_TYPE, REQUIRED);
|
60
|
+
add("protocol", STRING_TYPE, REQUIRED);
|
61
|
+
add("description", STRING_TYPE, OPTIONAL);
|
62
|
+
add("concurrency", INT_TYPE, OPTIONAL, -1);
|
63
|
+
add("accept_http_requests", BOOL_TYPE, OPTIONAL, false);
|
64
|
+
|
65
|
+
finalize();
|
66
|
+
}
|
67
|
+
};
|
68
|
+
|
69
|
+
string address;
|
70
|
+
string protocol;
|
71
|
+
string description;
|
72
|
+
/**
|
73
|
+
* Special values:
|
74
|
+
* 0 = unlimited concurrency
|
75
|
+
* < 0 = unknown
|
76
|
+
*/
|
77
|
+
int concurrency;
|
78
|
+
bool acceptHttpRequests;
|
79
|
+
|
80
|
+
Socket()
|
81
|
+
: concurrency(-1),
|
82
|
+
acceptHttpRequests(false)
|
83
|
+
{ }
|
84
|
+
|
85
|
+
Socket(const Schema &schema, const Json::Value &values) {
|
86
|
+
ConfigKit::Store store(schema);
|
87
|
+
vector<ConfigKit::Error> errors;
|
88
|
+
|
89
|
+
if (!store.update(values, errors)) {
|
90
|
+
throw ArgumentException("Invalid initial values: "
|
91
|
+
+ toString(errors));
|
92
|
+
}
|
93
|
+
|
94
|
+
address = store["address"].asString();
|
95
|
+
protocol = store["protocol"].asString();
|
96
|
+
if (!store["description"].isNull()) {
|
97
|
+
description = store["description"].asString();
|
98
|
+
}
|
99
|
+
concurrency = store["concurrency"].asInt();
|
100
|
+
acceptHttpRequests = store["accept_http_requests"].asBool();
|
101
|
+
}
|
102
|
+
|
103
|
+
Json::Value inspectAsJson() const {
|
104
|
+
Json::Value doc;
|
105
|
+
doc["address"] = address;
|
106
|
+
doc["protocol"] = protocol;
|
107
|
+
if (!description.empty()) {
|
108
|
+
doc["description"] = description;
|
109
|
+
}
|
110
|
+
doc["concurrency"] = concurrency;
|
111
|
+
doc["accept_http_requests"] = acceptHttpRequests;
|
112
|
+
return doc;
|
113
|
+
}
|
114
|
+
};
|
115
|
+
|
116
|
+
private:
|
117
|
+
void validate_autoGeneratedCode(vector<StaticString> &internalFieldErrors,
|
118
|
+
vector<StaticString> &appSuppliedFieldErrors) const;
|
119
|
+
|
120
|
+
public:
|
121
|
+
/****** Fields supplied by HandshakePrepare and HandshakePerform ******/
|
122
|
+
|
123
|
+
/**
|
124
|
+
* @hinted_parseable
|
125
|
+
* @require result.pid != -1
|
126
|
+
*/
|
127
|
+
pid_t pid;
|
128
|
+
|
129
|
+
/**
|
130
|
+
* If true, then indicates that this Process does not refer to a real OS
|
131
|
+
* process. The sockets in the socket list are fake and need not be deleted.
|
132
|
+
* Set to true by DummySpawner, used during unit tests.
|
133
|
+
*
|
134
|
+
* @hinted_parseable
|
135
|
+
*/
|
136
|
+
bool dummy;
|
137
|
+
|
138
|
+
/**
|
139
|
+
* @hinted_parseable
|
140
|
+
* @require_non_empty
|
141
|
+
*/
|
142
|
+
string gupid;
|
143
|
+
|
144
|
+
/**
|
145
|
+
* @hinted_parseable
|
146
|
+
*/
|
147
|
+
string codeRevision;
|
148
|
+
|
149
|
+
/**
|
150
|
+
* @hinted_parseable
|
151
|
+
*/
|
152
|
+
FileDescriptor stdinFd;
|
153
|
+
|
154
|
+
/**
|
155
|
+
* @hinted_parseable
|
156
|
+
*/
|
157
|
+
FileDescriptor stdoutAndErrFd;
|
158
|
+
|
159
|
+
/**
|
160
|
+
* @hinted_parseable
|
161
|
+
* @require result.spawnStartTime != 0
|
162
|
+
*/
|
163
|
+
unsigned long long spawnStartTime;
|
164
|
+
|
165
|
+
/**
|
166
|
+
* @hinted_parseable
|
167
|
+
* @require result.spawnEndTime != 0
|
168
|
+
*/
|
169
|
+
unsigned long long spawnEndTime;
|
170
|
+
|
171
|
+
/**
|
172
|
+
* @hinted_parseable
|
173
|
+
* @require result.spawnStartTimeMonotonic != 0
|
174
|
+
*/
|
175
|
+
MonotonicTimeUsec spawnStartTimeMonotonic;
|
176
|
+
|
177
|
+
/**
|
178
|
+
* @hinted_parseable
|
179
|
+
* @require result.spawnEndTimeMonotonic != 0
|
180
|
+
*/
|
181
|
+
MonotonicTimeUsec spawnEndTimeMonotonic;
|
182
|
+
|
183
|
+
|
184
|
+
/****** Fields supplied by the app ******/
|
185
|
+
|
186
|
+
vector<Socket> sockets;
|
187
|
+
|
188
|
+
|
189
|
+
Result()
|
190
|
+
: pid(-1),
|
191
|
+
dummy(false),
|
192
|
+
spawnStartTime(0),
|
193
|
+
spawnEndTime(0),
|
194
|
+
spawnStartTimeMonotonic(0),
|
195
|
+
spawnEndTimeMonotonic(0)
|
196
|
+
{ }
|
197
|
+
|
198
|
+
void initialize(const Context &context, const Config * const config) {
|
199
|
+
gupid = integerToHex(SystemTime::get() / 60) + "-" +
|
200
|
+
context.randomGenerator->generateAsciiString(10);
|
201
|
+
spawnStartTime = SystemTime::getUsec();
|
202
|
+
spawnStartTimeMonotonic = SystemTime::getMonotonicUsec();
|
203
|
+
}
|
204
|
+
|
205
|
+
bool validate(vector<StaticString> &internalFieldErrors,
|
206
|
+
vector<StaticString> &appSuppliedFieldErrors) const
|
207
|
+
{
|
208
|
+
validate_autoGeneratedCode(internalFieldErrors, appSuppliedFieldErrors);
|
209
|
+
|
210
|
+
if (sockets.empty()) {
|
211
|
+
appSuppliedFieldErrors.push_back(P_STATIC_STRING("sockets are not supplied"));
|
212
|
+
}
|
213
|
+
|
214
|
+
return internalFieldErrors.empty() && appSuppliedFieldErrors.empty();
|
215
|
+
}
|
44
216
|
};
|
217
|
+
// - end hinted parseable class -
|
45
218
|
|
46
219
|
|
47
220
|
} // namespace SpawningKit
|
48
221
|
} // namespace Passenger
|
49
222
|
|
223
|
+
#include <Core/SpawningKit/Result/AutoGeneratedCode.h>
|
224
|
+
|
50
225
|
#endif /* _PASSENGER_SPAWNING_KIT_RESULT_H_ */
|
@@ -0,0 +1,69 @@
|
|
1
|
+
/*
|
2
|
+
* Phusion Passenger - https://www.phusionpassenger.com/
|
3
|
+
* Copyright (c) 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
|
+
|
27
|
+
/*
|
28
|
+
* SpawningKit/Result/AutoGeneratedCode.h is automatically generated from
|
29
|
+
* SpawningKit/Result/AutoGeneratedCode.h.cxxcodebuilder by the build system.
|
30
|
+
* It uses the comment hints from SpawningKit/Result.h.
|
31
|
+
*
|
32
|
+
* To force regenerating this file:
|
33
|
+
* rm -f src/agent/Core/SpawningKit/Result/AutoGeneratedCode.h
|
34
|
+
* rake src/agent/Core/SpawningKit/Result/AutoGeneratedCode.h
|
35
|
+
*/
|
36
|
+
|
37
|
+
inline void
|
38
|
+
Passenger::SpawningKit::Result::validate_autoGeneratedCode(vector<StaticString> &internalFieldErrors, vector<StaticString> &appSuppliedFieldErrors) const {
|
39
|
+
const Result &result = *this;
|
40
|
+
|
41
|
+
if (OXT_UNLIKELY(!(result.pid != -1))) {
|
42
|
+
internalFieldErrors.push_back(P_STATIC_STRING("pid is not valid"));
|
43
|
+
}
|
44
|
+
if (OXT_UNLIKELY(gupid.empty())) {
|
45
|
+
internalFieldErrors.push_back(P_STATIC_STRING("gupid may not be empty"));
|
46
|
+
}
|
47
|
+
if (OXT_UNLIKELY(!(result.spawnStartTime != 0))) {
|
48
|
+
internalFieldErrors.push_back(P_STATIC_STRING("spawn_start_time is not valid"));
|
49
|
+
}
|
50
|
+
if (OXT_UNLIKELY(!(result.spawnEndTime != 0))) {
|
51
|
+
internalFieldErrors.push_back(P_STATIC_STRING("spawn_end_time is not valid"));
|
52
|
+
}
|
53
|
+
if (OXT_UNLIKELY(!(result.spawnStartTimeMonotonic != 0))) {
|
54
|
+
internalFieldErrors.push_back(P_STATIC_STRING("spawn_start_time_monotonic is not valid"));
|
55
|
+
}
|
56
|
+
if (OXT_UNLIKELY(!(result.spawnEndTimeMonotonic != 0))) {
|
57
|
+
internalFieldErrors.push_back(P_STATIC_STRING("spawn_end_time_monotonic is not valid"));
|
58
|
+
}
|
59
|
+
|
60
|
+
/*
|
61
|
+
* Excluded:
|
62
|
+
*
|
63
|
+
* dummy
|
64
|
+
* codeRevision
|
65
|
+
* stdinFd
|
66
|
+
* stdoutAndErrFd
|
67
|
+
*/
|
68
|
+
}
|
69
|
+
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# Phusion Passenger - https://www.phusionpassenger.com/
|
2
|
+
# Copyright (c) 2017 Phusion Holding B.V.
|
3
|
+
#
|
4
|
+
# "Passenger", "Phusion Passenger" and "Union Station" are registered
|
5
|
+
# trademarks of Phusion Holding B.V.
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in
|
15
|
+
# all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
23
|
+
# THE SOFTWARE.
|
24
|
+
|
25
|
+
require 'build/support/vendor/cxx_hinted_parser/lib/cxx_hinted_parser'
|
26
|
+
|
27
|
+
def main
|
28
|
+
result_class_fields = parse_result_class_fields
|
29
|
+
comment copyright_header_for(__FILE__), 1
|
30
|
+
separator
|
31
|
+
|
32
|
+
comment %q{
|
33
|
+
SpawningKit/Result/AutoGeneratedCode.h is automatically generated from
|
34
|
+
SpawningKit/Result/AutoGeneratedCode.h.cxxcodebuilder by the build system.
|
35
|
+
It uses the comment hints from SpawningKit/Result.h.
|
36
|
+
|
37
|
+
To force regenerating this file:
|
38
|
+
rm -f src/agent/Core/SpawningKit/Result/AutoGeneratedCode.h
|
39
|
+
rake src/agent/Core/SpawningKit/Result/AutoGeneratedCode.h
|
40
|
+
}
|
41
|
+
|
42
|
+
separator
|
43
|
+
|
44
|
+
|
45
|
+
function 'inline void Passenger::SpawningKit::Result::validate_autoGeneratedCode(' \
|
46
|
+
'vector<StaticString> &internalFieldErrors, ' \
|
47
|
+
'vector<StaticString> &appSuppliedFieldErrors) const' \
|
48
|
+
do
|
49
|
+
add_code %q{
|
50
|
+
const Result &result = *this;
|
51
|
+
}
|
52
|
+
|
53
|
+
separator
|
54
|
+
excluded_field_names = []
|
55
|
+
|
56
|
+
result_class_fields.each do |field|
|
57
|
+
if field.metadata[:supplied_by_app]
|
58
|
+
error_collection = 'appSuppliedFieldErrors'
|
59
|
+
else
|
60
|
+
error_collection = 'internalFieldErrors'
|
61
|
+
end
|
62
|
+
if field.metadata[:require_non_empty]
|
63
|
+
add_code %Q{
|
64
|
+
if (OXT_UNLIKELY(#{field.name}.empty())) {
|
65
|
+
#{error_collection}.push_back(P_STATIC_STRING("#{filename_for(field)} may not be empty"));
|
66
|
+
}
|
67
|
+
}
|
68
|
+
elsif field.metadata[:require]
|
69
|
+
add_code %Q{
|
70
|
+
if (OXT_UNLIKELY(!(#{field.metadata[:require]}))) {
|
71
|
+
#{error_collection}.push_back(P_STATIC_STRING("#{filename_for(field)} is not valid"));
|
72
|
+
}
|
73
|
+
}
|
74
|
+
else
|
75
|
+
excluded_field_names << field.name
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
separator
|
80
|
+
comment "Excluded:\n\n#{excluded_field_names.join("\n")}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def filename_for(field)
|
85
|
+
key = field.metadata[:supplied_by_app]
|
86
|
+
if key.is_a?(String)
|
87
|
+
key
|
88
|
+
else
|
89
|
+
field.name.gsub(/([A-Z])/, '_\1').downcase
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def read_expression_for(field)
|
94
|
+
case field.type
|
95
|
+
when 'string'
|
96
|
+
%Q{strip(readAll(dir + "/#{filename_for(field)}"))}
|
97
|
+
when 'vector<Socket>'
|
98
|
+
%Q{parseSocketJsonFile(dir + "/#{filename_for(field)}")}
|
99
|
+
else
|
100
|
+
raise "Unsupported field type '#{field.type}' for field #{field.name}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def parse_result_class_fields
|
105
|
+
result_h = File.dirname(__FILE__) + '/../Result.h'
|
106
|
+
parser = CxxHintedParser::Parser.load_file(result_h).parse
|
107
|
+
parser.structs['Result']
|
108
|
+
end
|
109
|
+
|
110
|
+
main
|
@@ -26,14 +26,45 @@
|
|
26
26
|
#ifndef _PASSENGER_SPAWNING_KIT_SMART_SPAWNER_H_
|
27
27
|
#define _PASSENGER_SPAWNING_KIT_SMART_SPAWNER_H_
|
28
28
|
|
29
|
-
#include <
|
30
|
-
#include <
|
31
|
-
#include <
|
32
|
-
#include <
|
33
|
-
#include <
|
29
|
+
#include <oxt/thread.hpp>
|
30
|
+
#include <oxt/system_calls.hpp>
|
31
|
+
#include <boost/bind.hpp>
|
32
|
+
#include <boost/make_shared.hpp>
|
33
|
+
#include <string>
|
34
|
+
#include <vector>
|
35
|
+
#include <map>
|
36
|
+
#include <stdexcept>
|
37
|
+
#include <dirent.h>
|
38
|
+
#include <sys/types.h>
|
39
|
+
#include <sys/wait.h>
|
40
|
+
#include <sys/stat.h>
|
41
|
+
#include <signal.h>
|
42
|
+
#include <cstdio>
|
43
|
+
#include <cstring>
|
44
|
+
#include <cassert>
|
34
45
|
|
35
46
|
#include <adhoc_lve.h>
|
36
47
|
|
48
|
+
#include <LoggingKit/Logging.h>
|
49
|
+
#include <Constants.h>
|
50
|
+
#include <Exceptions.h>
|
51
|
+
#include <DataStructures/StringKeyTable.h>
|
52
|
+
#include <ProcessManagement/Utils.h>
|
53
|
+
#include <Utils/SystemTime.h>
|
54
|
+
#include <Utils/IOUtils.h>
|
55
|
+
#include <Utils/BufferedIO.h>
|
56
|
+
#include <Utils/JsonUtils.h>
|
57
|
+
#include <Utils/ScopeGuard.h>
|
58
|
+
#include <Utils/ProcessMetricsCollector.h>
|
59
|
+
#include <LveLoggingDecorator.h>
|
60
|
+
#include <Core/SpawningKit/Spawner.h>
|
61
|
+
#include <Core/SpawningKit/Exceptions.h>
|
62
|
+
#include <Core/SpawningKit/PipeWatcher.h>
|
63
|
+
#include <Core/SpawningKit/Handshake/Session.h>
|
64
|
+
#include <Core/SpawningKit/Handshake/Prepare.h>
|
65
|
+
#include <Core/SpawningKit/Handshake/Perform.h>
|
66
|
+
#include <Core/SpawningKit/Handshake/BackgroundIOCapturer.h>
|
67
|
+
|
37
68
|
namespace Passenger {
|
38
69
|
namespace SpawningKit {
|
39
70
|
|
@@ -42,33 +73,14 @@ using namespace boost;
|
|
42
73
|
using namespace oxt;
|
43
74
|
|
44
75
|
|
45
|
-
class SmartSpawner: public Spawner
|
76
|
+
class SmartSpawner: public Spawner {
|
46
77
|
private:
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
pid_t pid;
|
54
|
-
FileDescriptor adminSocket;
|
55
|
-
BufferedIO io;
|
56
|
-
BackgroundIOCapturerPtr stderrCapturer;
|
57
|
-
DebugDirPtr debugDir;
|
58
|
-
const Options *options;
|
59
|
-
|
60
|
-
/****** Working state ******/
|
61
|
-
unsigned long long timeout;
|
62
|
-
|
63
|
-
StartupDetails() {
|
64
|
-
options = NULL;
|
65
|
-
timeout = 0;
|
66
|
-
}
|
67
|
-
};
|
68
|
-
|
69
|
-
const vector<string> preloaderCommand;
|
70
|
-
map<string, string> preloaderAnnotations;
|
71
|
-
Options options;
|
78
|
+
const string preloaderCommandString;
|
79
|
+
string preloaderEnvvars;
|
80
|
+
string preloaderUserInfo;
|
81
|
+
string preloaderUlimits;
|
82
|
+
StringKeyTable<string> preloaderAnnotations;
|
83
|
+
AppPoolOptions options;
|
72
84
|
|
73
85
|
// Protects m_lastUsed and pid.
|
74
86
|
mutable boost::mutex simpleFieldSyncher;
|
@@ -77,128 +89,212 @@ private:
|
|
77
89
|
|
78
90
|
// Preloader information.
|
79
91
|
pid_t pid;
|
80
|
-
FileDescriptor
|
92
|
+
FileDescriptor preloaderStdin;
|
81
93
|
string socketAddress;
|
82
94
|
unsigned long long m_lastUsed;
|
83
|
-
// Upon starting the preloader, its preparation info is stored here
|
84
|
-
// for future reference.
|
85
|
-
SpawnPreparationInfo preparation;
|
86
95
|
|
87
|
-
|
96
|
+
|
97
|
+
/**
|
98
|
+
* Behaves like <tt>waitpid(pid, status, WNOHANG)</tt>, but waits at most
|
99
|
+
* <em>timeout</em> miliseconds for the process to exit.
|
100
|
+
*/
|
101
|
+
static int timedWaitpid(pid_t pid, int *status, unsigned long long timeout) {
|
102
|
+
Timer<SystemTime::GRAN_10MSEC> timer;
|
103
|
+
int ret;
|
104
|
+
|
105
|
+
do {
|
106
|
+
ret = syscalls::waitpid(pid, status, WNOHANG);
|
107
|
+
if (ret > 0 || ret == -1) {
|
108
|
+
return ret;
|
109
|
+
} else {
|
110
|
+
syscalls::usleep(10000);
|
111
|
+
}
|
112
|
+
} while (timer.elapsed() < timeout);
|
113
|
+
return 0; // timed out
|
114
|
+
}
|
115
|
+
|
116
|
+
static bool osProcessExists(pid_t pid) {
|
117
|
+
if (syscalls::kill(pid, 0) == 0) {
|
118
|
+
/* On some environments, e.g. Heroku, the init process does
|
119
|
+
* not properly reap adopted zombie processes, which can interfere
|
120
|
+
* with our process existance check. To work around this, we
|
121
|
+
* explicitly check whether or not the process has become a zombie.
|
122
|
+
*/
|
123
|
+
return !isZombie(pid);
|
124
|
+
} else {
|
125
|
+
return errno != ESRCH;
|
126
|
+
}
|
127
|
+
}
|
128
|
+
|
129
|
+
static bool isZombie(pid_t pid) {
|
130
|
+
string filename = "/proc/" + toString(pid) + "/status";
|
131
|
+
FILE *f = fopen(filename.c_str(), "r");
|
132
|
+
if (f == NULL) {
|
133
|
+
// Don't know.
|
134
|
+
return false;
|
135
|
+
}
|
136
|
+
|
137
|
+
bool result = false;
|
138
|
+
while (!feof(f)) {
|
139
|
+
char buf[512];
|
140
|
+
const char *line;
|
141
|
+
|
142
|
+
line = fgets(buf, sizeof(buf), f);
|
143
|
+
if (line == NULL) {
|
144
|
+
break;
|
145
|
+
}
|
146
|
+
if (strcmp(line, "State: Z (zombie)\n") == 0) {
|
147
|
+
// Is a zombie.
|
148
|
+
result = true;
|
149
|
+
break;
|
150
|
+
}
|
151
|
+
}
|
152
|
+
fclose(f);
|
153
|
+
return result;
|
154
|
+
}
|
155
|
+
|
156
|
+
static string createCommandString(const vector<string> &command) {
|
88
157
|
string result;
|
89
|
-
|
158
|
+
vector<string>::const_iterator it;
|
159
|
+
vector<string>::const_iterator begin = command.begin();
|
160
|
+
vector<string>::const_iterator end = command.end();
|
90
161
|
|
91
|
-
for (
|
92
|
-
if (
|
93
|
-
result.append(1, '
|
162
|
+
for (it = begin; it != end; it++) {
|
163
|
+
if (it != begin) {
|
164
|
+
result.append(1, ' ');
|
94
165
|
}
|
95
|
-
result.append(
|
166
|
+
result.append(escapeShell(*it));
|
96
167
|
}
|
168
|
+
|
97
169
|
return result;
|
98
170
|
}
|
99
171
|
|
100
|
-
|
101
|
-
|
172
|
+
void setConfigFromAppPoolOptions(Config *config, Json::Value &extraArgs,
|
173
|
+
const AppPoolOptions &options)
|
102
174
|
{
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
175
|
+
Spawner::setConfigFromAppPoolOptions(config, extraArgs, options);
|
176
|
+
config->spawnMethod = P_STATIC_STRING("smart");
|
177
|
+
}
|
178
|
+
|
179
|
+
struct StdChannelsAsyncOpenState {
|
180
|
+
oxt::thread *stdinOpenThread;
|
181
|
+
FileDescriptor stdinFd;
|
182
|
+
int stdinOpenErrno;
|
183
|
+
|
184
|
+
oxt::thread *stdoutAndErrOpenThread;
|
185
|
+
FileDescriptor stdoutAndErrFd;
|
186
|
+
int stdoutAndErrOpenErrno;
|
187
|
+
|
188
|
+
BackgroundIOCapturerPtr stdoutAndErrCapturer;
|
189
|
+
|
190
|
+
StdChannelsAsyncOpenState()
|
191
|
+
: stdinOpenThread(NULL),
|
192
|
+
stdoutAndErrOpenThread(NULL)
|
193
|
+
{ }
|
194
|
+
|
195
|
+
~StdChannelsAsyncOpenState() {
|
196
|
+
boost::this_thread::disable_interruption di;
|
197
|
+
boost::this_thread::disable_syscall_interruption dsi;
|
198
|
+
if (stdinOpenThread != NULL) {
|
199
|
+
stdinOpenThread->interrupt_and_join();
|
200
|
+
delete stdinOpenThread;
|
201
|
+
}
|
202
|
+
if (stdoutAndErrOpenThread != NULL) {
|
203
|
+
stdoutAndErrOpenThread->interrupt_and_join();
|
204
|
+
delete stdoutAndErrOpenThread;
|
113
205
|
}
|
114
|
-
command.push_back("exec \"$@\"");
|
115
|
-
command.push_back("SpawnPreparerShell");
|
116
|
-
} else {
|
117
|
-
command.push_back(agentFilename);
|
118
|
-
}
|
119
|
-
command.push_back(agentFilename);
|
120
|
-
command.push_back("spawn-preparer");
|
121
|
-
command.push_back(preparation.appRoot);
|
122
|
-
command.push_back(serializeEnvvarsFromPoolOptions(options));
|
123
|
-
command.push_back(preloaderCommand[0]);
|
124
|
-
// Note: do not try to set a process title here.
|
125
|
-
// https://code.google.com/p/phusion-passenger/issues/detail?id=855
|
126
|
-
command.push_back(preloaderCommand[0]);
|
127
|
-
for (unsigned int i = 1; i < preloaderCommand.size(); i++) {
|
128
|
-
command.push_back(preloaderCommand[i]);
|
129
206
|
}
|
207
|
+
};
|
130
208
|
|
131
|
-
|
132
|
-
return command;
|
133
|
-
}
|
209
|
+
typedef boost::shared_ptr<StdChannelsAsyncOpenState> StdChannelsAsyncOpenStatePtr;
|
134
210
|
|
135
|
-
|
136
|
-
|
137
|
-
StartupDetails &details)
|
211
|
+
StdChannelsAsyncOpenStatePtr openStdChannelsFifosAsynchronously(
|
212
|
+
HandshakeSession &session)
|
138
213
|
{
|
139
|
-
|
140
|
-
|
214
|
+
StdChannelsAsyncOpenStatePtr state = boost::make_shared<StdChannelsAsyncOpenState>();
|
215
|
+
state->stdinOpenThread = new oxt::thread(boost::bind(
|
216
|
+
openStdinChannel, state, session.workDir->getPath()),
|
217
|
+
"FIFO opener: " + session.workDir->getPath() + "/stdin", 1024 * 128);
|
218
|
+
state->stdoutAndErrOpenThread = new oxt::thread(boost::bind(
|
219
|
+
openStdoutAndErrChannel, state, session.workDir->getPath()),
|
220
|
+
"FIFO opener: " + session.workDir->getPath() + "/stdout_and_err", 1024 * 128);
|
221
|
+
return state;
|
141
222
|
}
|
142
223
|
|
143
|
-
void
|
144
|
-
|
145
|
-
BackgroundIOCapturerPtr &stderrCapturer,
|
146
|
-
const Options &options,
|
147
|
-
const DebugDirPtr &debugDir)
|
224
|
+
void waitForStdChannelFifosToBeOpenedByPeer(const StdChannelsAsyncOpenStatePtr &state,
|
225
|
+
HandshakeSession &session, pid_t pid)
|
148
226
|
{
|
149
227
|
TRACE_POINT();
|
150
|
-
|
151
|
-
|
152
|
-
string stderrOutput;
|
153
|
-
if (stderrCapturer != NULL) {
|
154
|
-
stderrOutput = stderrCapturer->stop();
|
155
|
-
}
|
156
|
-
|
157
|
-
// If the exception wasn't due to a timeout, try to capture the
|
158
|
-
// remaining stderr output for at most 2 seconds.
|
159
|
-
if (errorKind != SpawnException::PRELOADER_STARTUP_TIMEOUT
|
160
|
-
&& errorKind != SpawnException::APP_STARTUP_TIMEOUT
|
161
|
-
&& stderrCapturer != NULL)
|
162
|
-
{
|
163
|
-
bool done = false;
|
164
|
-
unsigned long long timeout = 2000;
|
165
|
-
while (!done) {
|
166
|
-
char buf[1024 * 32];
|
167
|
-
unsigned int ret;
|
228
|
+
MonotonicTimeUsec startTime = SystemTime::getMonotonicUsec();
|
229
|
+
ScopeGuard guard(boost::bind(adjustTimeout, startTime, &session.timeoutUsec));
|
168
230
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
231
|
+
try {
|
232
|
+
if (state->stdinOpenThread->try_join_for(
|
233
|
+
boost::chrono::microseconds(session.timeoutUsec)))
|
234
|
+
{
|
235
|
+
delete state->stdinOpenThread;
|
236
|
+
state->stdinOpenThread = NULL;
|
237
|
+
if (state->stdinFd == -1) {
|
238
|
+
throw SystemException("Error opening FIFO "
|
239
|
+
+ session.workDir->getPath() + "/stdin",
|
240
|
+
state->stdinOpenErrno);
|
241
|
+
} else {
|
242
|
+
P_LOG_FILE_DESCRIPTOR_PURPOSE(state->stdinFd,
|
243
|
+
"App " << pid << " (" << options.appRoot
|
244
|
+
<< ") stdin");
|
245
|
+
adjustTimeout(startTime, &session.timeoutUsec);
|
246
|
+
startTime = SystemTime::getMonotonicUsec();
|
182
247
|
}
|
248
|
+
} else {
|
249
|
+
throw TimeoutException("Timeout opening FIFO "
|
250
|
+
+ session.workDir->getPath() + "/stdin");
|
183
251
|
}
|
252
|
+
|
253
|
+
UPDATE_TRACE_POINT();
|
254
|
+
if (state->stdoutAndErrOpenThread->try_join_for(
|
255
|
+
boost::chrono::microseconds(session.timeoutUsec)))
|
256
|
+
{
|
257
|
+
delete state->stdoutAndErrOpenThread;
|
258
|
+
state->stdoutAndErrOpenThread = NULL;
|
259
|
+
if (state->stdoutAndErrFd == -1) {
|
260
|
+
throw SystemException("Error opening FIFO "
|
261
|
+
+ session.workDir->getPath() + "/stdout_and_err",
|
262
|
+
state->stdoutAndErrOpenErrno);
|
263
|
+
} else {
|
264
|
+
P_LOG_FILE_DESCRIPTOR_PURPOSE(state->stdoutAndErrFd,
|
265
|
+
"App " << pid << " (" << options.appRoot
|
266
|
+
<< ") stdoutAndErr");
|
267
|
+
}
|
268
|
+
} else {
|
269
|
+
throw TimeoutException("Timeout opening FIFO "
|
270
|
+
+ session.workDir->getPath() + "/stdout_and_err");
|
271
|
+
}
|
272
|
+
|
273
|
+
state->stdoutAndErrCapturer = boost::make_shared<BackgroundIOCapturer>(
|
274
|
+
state->stdoutAndErrFd, pid, session.config->appGroupName,
|
275
|
+
session.config->logFile);
|
276
|
+
state->stdoutAndErrCapturer->start();
|
277
|
+
} catch (const boost::system::system_error &e) {
|
278
|
+
throw SystemException(e.what(), e.code().value());
|
184
279
|
}
|
185
|
-
|
280
|
+
}
|
186
281
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
e
|
194
|
-
annotatePreloaderException(e, debugDir);
|
195
|
-
throwSpawnException(e, options);
|
282
|
+
static void openStdinChannel(StdChannelsAsyncOpenStatePtr state,
|
283
|
+
const string &workDir)
|
284
|
+
{
|
285
|
+
int fd = syscalls::open((workDir + "/stdin").c_str(), O_WRONLY | O_APPEND);
|
286
|
+
int e = errno;
|
287
|
+
state->stdinFd.assign(fd, __FILE__, __LINE__);
|
288
|
+
state->stdinOpenErrno = e;
|
196
289
|
}
|
197
290
|
|
198
|
-
void
|
199
|
-
|
200
|
-
|
201
|
-
|
291
|
+
static void openStdoutAndErrChannel(StdChannelsAsyncOpenStatePtr state,
|
292
|
+
const string &workDir)
|
293
|
+
{
|
294
|
+
int fd = syscalls::open((workDir + "/stdout_and_err").c_str(), O_RDONLY);
|
295
|
+
int e = errno;
|
296
|
+
state->stdoutAndErrFd.assign(fd, __FILE__, __LINE__);
|
297
|
+
state->stdoutAndErrOpenErrno = e;
|
202
298
|
}
|
203
299
|
|
204
300
|
bool preloaderStarted() const {
|
@@ -211,121 +307,145 @@ private:
|
|
211
307
|
boost::this_thread::disable_syscall_interruption dsi;
|
212
308
|
assert(!preloaderStarted());
|
213
309
|
P_DEBUG("Spawning new preloader: appRoot=" << options.appRoot);
|
214
|
-
checkChrootDirectories(options);
|
215
310
|
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
311
|
+
Config config;
|
312
|
+
Json::Value extraArgs;
|
313
|
+
try {
|
314
|
+
setConfigFromAppPoolOptions(&config, extraArgs, options);
|
315
|
+
config.startCommand = preloaderCommandString;
|
316
|
+
} catch (const std::exception &originalException) {
|
317
|
+
Journey journey(SPAWN_THROUGH_PRELOADER, true);
|
318
|
+
journey.setStepErrored(SPAWNING_KIT_PREPARATION, true);
|
319
|
+
throw SpawnException(originalException, journey,
|
320
|
+
&config).finalize();
|
321
|
+
}
|
223
322
|
|
323
|
+
HandshakeSession session(*context, config, START_PRELOADER);
|
324
|
+
session.journey.setStepInProgress(SPAWNING_KIT_PREPARATION);
|
325
|
+
|
326
|
+
try {
|
327
|
+
internalStartPreloader(config, session, extraArgs);
|
328
|
+
} catch (const SpawnException &) {
|
329
|
+
throw;
|
330
|
+
} catch (const std::exception &originalException) {
|
331
|
+
session.journey.setStepErrored(SPAWNING_KIT_PREPARATION);
|
332
|
+
throw SpawnException(originalException, session.journey,
|
333
|
+
&config).finalize();
|
334
|
+
}
|
335
|
+
}
|
336
|
+
|
337
|
+
void internalStartPreloader(Config &config, HandshakeSession &session,
|
338
|
+
const Json::Value &extraArgs)
|
339
|
+
{
|
340
|
+
TRACE_POINT();
|
341
|
+
HandshakePrepare(session, extraArgs).execute();
|
342
|
+
Pipe stdinChannel = createPipe(__FILE__, __LINE__);
|
343
|
+
Pipe stdoutAndErrChannel = createPipe(__FILE__, __LINE__);
|
224
344
|
adhoc_lve::LveEnter scopedLveEnter(LveLoggingDecorator::lveInitOnce(),
|
225
|
-
|
226
|
-
|
227
|
-
|
345
|
+
session.uid,
|
346
|
+
config.lveMinUid,
|
347
|
+
LveLoggingDecorator::lveExitCallback);
|
228
348
|
LveLoggingDecorator::logLveEnter(scopedLveEnter,
|
229
|
-
|
230
|
-
|
349
|
+
session.uid,
|
350
|
+
config.lveMinUid);
|
351
|
+
string agentFilename = context->resourceLocator
|
352
|
+
->findSupportBinary(AGENT_EXE);
|
353
|
+
|
354
|
+
session.journey.setStepPerformed(SPAWNING_KIT_PREPARATION);
|
355
|
+
session.journey.setStepInProgress(SPAWNING_KIT_FORK_SUBPROCESS);
|
356
|
+
session.journey.setStepInProgress(SUBPROCESS_BEFORE_FIRST_EXEC);
|
357
|
+
|
231
358
|
pid_t pid = syscalls::fork();
|
232
359
|
if (pid == 0) {
|
233
|
-
setenv("PASSENGER_DEBUG_DIR", debugDir->getPath().c_str(), 1);
|
234
360
|
purgeStdio(stdout);
|
235
361
|
purgeStdio(stderr);
|
236
362
|
resetSignalHandlersAndMask();
|
237
363
|
disableMallocDebugging();
|
238
|
-
int
|
239
|
-
int
|
240
|
-
dup2(
|
241
|
-
dup2(
|
242
|
-
dup2(
|
243
|
-
closeAllFileDescriptors(2);
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
364
|
+
int stdinCopy = dup2(stdinChannel.first, 3);
|
365
|
+
int stdoutAndErrCopy = dup2(stdoutAndErrChannel.second, 4);
|
366
|
+
dup2(stdinCopy, 0);
|
367
|
+
dup2(stdoutAndErrCopy, 1);
|
368
|
+
dup2(stdoutAndErrCopy, 2);
|
369
|
+
closeAllFileDescriptors(2, true);
|
370
|
+
execlp(agentFilename.c_str(),
|
371
|
+
agentFilename.c_str(),
|
372
|
+
"spawn-env-setupper",
|
373
|
+
session.workDir->getPath().c_str(),
|
374
|
+
"--before",
|
375
|
+
NULL);
|
249
376
|
|
250
377
|
int e = errno;
|
251
|
-
printf("!> Error\n");
|
252
|
-
printf("!> \n");
|
253
|
-
printf("Cannot execute \"%s\": %s (errno=%d)\n", command[0].c_str(),
|
254
|
-
strerror(e), e);
|
255
378
|
fprintf(stderr, "Cannot execute \"%s\": %s (errno=%d)\n",
|
256
|
-
|
257
|
-
fflush(stdout);
|
379
|
+
agentFilename.c_str(), strerror(e), e);
|
258
380
|
fflush(stderr);
|
259
381
|
_exit(1);
|
260
382
|
|
261
383
|
} else if (pid == -1) {
|
262
384
|
int e = errno;
|
263
|
-
|
385
|
+
UPDATE_TRACE_POINT();
|
386
|
+
session.journey.setStepErrored(SPAWNING_KIT_FORK_SUBPROCESS);
|
387
|
+
SpawnException ex(OPERATING_SYSTEM_ERROR, session.journey, &config);
|
388
|
+
ex.setSummary(StaticString("Cannot fork a new process: ") + strerror(e)
|
389
|
+
+ " (errno=" + toString(e) + ")");
|
390
|
+
ex.setAdvancedProblemDetails(StaticString("Cannot fork a new process: ")
|
391
|
+
+ strerror(e) + " (errno=" + toString(e) + ")");
|
392
|
+
throw ex.finalize();
|
264
393
|
|
265
394
|
} else {
|
395
|
+
UPDATE_TRACE_POINT();
|
396
|
+
session.journey.setStepPerformed(SPAWNING_KIT_FORK_SUBPROCESS);
|
397
|
+
session.journey.setStepInProgress(SPAWNING_KIT_HANDSHAKE_PERFORM);
|
398
|
+
|
266
399
|
scopedLveEnter.exit();
|
267
400
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
"Preloader " << pid << " (" << options.appRoot << ") adminSocket[1]");
|
273
|
-
P_LOG_FILE_DESCRIPTOR_PURPOSE(errorPipe.first,
|
274
|
-
"Preloader " << pid << " (" << options.appRoot << ") errorPipe[0]");
|
275
|
-
P_LOG_FILE_DESCRIPTOR_PURPOSE(errorPipe.second,
|
276
|
-
"Preloader " << pid << " (" << options.appRoot << ") errorPipe[1]");
|
401
|
+
P_LOG_FILE_DESCRIPTOR_PURPOSE(stdinChannel.second,
|
402
|
+
"Preloader " << pid << " (" << options.appRoot << ") stdin");
|
403
|
+
P_LOG_FILE_DESCRIPTOR_PURPOSE(stdoutAndErrChannel.first,
|
404
|
+
"Preloader " << pid << " (" << options.appRoot << ") stdoutAndErr");
|
277
405
|
|
278
406
|
UPDATE_TRACE_POINT();
|
279
407
|
ScopeGuard guard(boost::bind(nonInterruptableKillAndWaitpid, pid));
|
280
|
-
P_DEBUG("Preloader process forked for appRoot=" << options.appRoot
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
details.stderrCapturer->start();
|
297
|
-
details.debugDir = debugDir;
|
298
|
-
details.options = &options;
|
299
|
-
details.timeout = options.startTimeout * 1000;
|
408
|
+
P_DEBUG("Preloader process forked for appRoot=" << options.appRoot
|
409
|
+
<< ": PID " << pid);
|
410
|
+
stdinChannel.first.close();
|
411
|
+
stdoutAndErrChannel.second.close();
|
412
|
+
|
413
|
+
HandshakePerform(session, pid, stdinChannel.second,
|
414
|
+
stdoutAndErrChannel.first).execute();
|
415
|
+
string envvars, userInfo, ulimits;
|
416
|
+
// If a new output variable was added to this function,
|
417
|
+
// then don't forget to also update these locations:
|
418
|
+
// - the critical section below
|
419
|
+
// - bottom of stopPreloader()
|
420
|
+
// - addPreloaderEnvDumps()
|
421
|
+
HandshakePerform::loadBasicInfoFromEnvDumpDir(session.envDumpDir,
|
422
|
+
envvars, userInfo, ulimits);
|
423
|
+
string socketAddress = findPreloaderCommandSocketAddress(session);
|
300
424
|
|
301
|
-
{
|
302
|
-
boost::this_thread::restore_interruption ri(di);
|
303
|
-
boost::this_thread::restore_syscall_interruption rsi(dsi);
|
304
|
-
socketAddress = negotiatePreloaderStartup(details);
|
305
|
-
}
|
306
|
-
this->adminSocket = adminSocket.second;
|
307
425
|
{
|
308
426
|
boost::lock_guard<boost::mutex> l(simpleFieldSyncher);
|
309
427
|
this->pid = pid;
|
428
|
+
this->socketAddress = socketAddress;
|
429
|
+
this->preloaderStdin = stdinChannel.second;
|
430
|
+
this->preloaderEnvvars = envvars;
|
431
|
+
this->preloaderUserInfo = userInfo;
|
432
|
+
this->preloaderUlimits = ulimits;
|
433
|
+
this->preloaderAnnotations = loadAnnotationsFromEnvDumpDir(
|
434
|
+
session.envDumpDir);
|
310
435
|
}
|
311
436
|
|
312
|
-
PipeWatcherPtr watcher
|
313
|
-
|
314
|
-
|
315
|
-
adminSocket.second, "stdout", pid, options.getAppGroupName(), options.appLogFile);
|
316
|
-
watcher->initialize();
|
317
|
-
watcher->start();
|
318
|
-
|
319
|
-
watcher = boost::make_shared<PipeWatcher>(config,
|
320
|
-
errorPipe.first, "stderr", pid, options.getAppGroupName(), options.appLogFile);
|
437
|
+
PipeWatcherPtr watcher = boost::make_shared<PipeWatcher>(
|
438
|
+
stdoutAndErrChannel.first, "output", config.appGroupName,
|
439
|
+
config.logFile, pid);
|
321
440
|
watcher->initialize();
|
322
441
|
watcher->start();
|
323
442
|
|
324
|
-
|
443
|
+
UPDATE_TRACE_POINT();
|
444
|
+
guard.clear();
|
445
|
+
session.journey.setStepPerformed(SPAWNING_KIT_HANDSHAKE_PERFORM);
|
325
446
|
P_INFO("Preloader for " << options.appRoot <<
|
326
447
|
" started on PID " << pid <<
|
327
448
|
", listening on " << socketAddress);
|
328
|
-
guard.clear();
|
329
449
|
}
|
330
450
|
}
|
331
451
|
|
@@ -337,445 +457,742 @@ private:
|
|
337
457
|
if (!preloaderStarted()) {
|
338
458
|
return;
|
339
459
|
}
|
340
|
-
|
460
|
+
|
461
|
+
preloaderStdin.close(false);
|
462
|
+
|
341
463
|
if (timedWaitpid(pid, NULL, 5000) == 0) {
|
342
|
-
|
464
|
+
P_DEBUG("Preloader did not exit in time, killing it...");
|
343
465
|
syscalls::kill(pid, SIGKILL);
|
344
466
|
syscalls::waitpid(pid, NULL, 0);
|
345
467
|
}
|
468
|
+
|
346
469
|
// Delete socket after the process has exited so that it
|
347
470
|
// doesn't crash upon deleting a nonexistant file.
|
348
471
|
if (getSocketAddressType(socketAddress) == SAT_UNIX) {
|
349
472
|
string filename = parseUnixSocketAddress(socketAddress);
|
350
473
|
syscalls::unlink(filename.c_str());
|
351
474
|
}
|
475
|
+
|
352
476
|
{
|
353
477
|
boost::lock_guard<boost::mutex> l(simpleFieldSyncher);
|
354
478
|
pid = -1;
|
479
|
+
socketAddress.clear();
|
480
|
+
preloaderEnvvars.clear();
|
481
|
+
preloaderUserInfo.clear();
|
482
|
+
preloaderUlimits.clear();
|
483
|
+
preloaderAnnotations.clear();
|
355
484
|
}
|
356
|
-
socketAddress.clear();
|
357
|
-
preparation = SpawnPreparationInfo();
|
358
485
|
}
|
359
486
|
|
360
|
-
|
487
|
+
FileDescriptor connectToPreloader(HandshakeSession &session) {
|
361
488
|
TRACE_POINT();
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
"passenger_version: " PASSENGER_VERSION "\n"
|
368
|
-
"UNIX_PATH_MAX: " + toString(UNIX_PATH_MAX) + "\n";
|
369
|
-
if (!details.options->apiKey.empty()) {
|
370
|
-
data.append("connect_password: " + details.options->apiKey + "\n");
|
371
|
-
}
|
372
|
-
if (!config->instanceDir.empty()) {
|
373
|
-
data.append("instance_dir: " + config->instanceDir + "\n");
|
374
|
-
data.append("socket_dir: " + config->instanceDir + "/apps.s\n");
|
375
|
-
}
|
489
|
+
FileDescriptor fd(connectToServer(socketAddress, __FILE__, __LINE__), NULL, 0);
|
490
|
+
P_LOG_FILE_DESCRIPTOR_PURPOSE(fd, "Preloader " << pid
|
491
|
+
<< " (" << session.config->appRoot << ") connection");
|
492
|
+
return fd;
|
493
|
+
}
|
376
494
|
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
}
|
495
|
+
struct ForkResult {
|
496
|
+
pid_t pid;
|
497
|
+
FileDescriptor stdinFd;
|
498
|
+
FileDescriptor stdoutAndErrFd;
|
499
|
+
string alreadyReadStdoutAndErrData;
|
500
|
+
|
501
|
+
ForkResult()
|
502
|
+
: pid(-1)
|
503
|
+
{ }
|
504
|
+
|
505
|
+
ForkResult(pid_t _pid, const FileDescriptor &_stdinFd,
|
506
|
+
const FileDescriptor &_stdoutAndErrFd,
|
507
|
+
const string &_alreadyReadStdoutAndErrData)
|
508
|
+
: pid(_pid),
|
509
|
+
stdinFd(_stdinFd),
|
510
|
+
stdoutAndErrFd(_stdoutAndErrFd),
|
511
|
+
alreadyReadStdoutAndErrData(_alreadyReadStdoutAndErrData)
|
512
|
+
{ }
|
513
|
+
};
|
386
514
|
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
515
|
+
struct PreloaderCrashed {
|
516
|
+
SystemException *systemException;
|
517
|
+
IOException *ioException;
|
518
|
+
|
519
|
+
PreloaderCrashed(const SystemException &e)
|
520
|
+
: systemException(new SystemException(e)),
|
521
|
+
ioException(NULL)
|
522
|
+
{ }
|
523
|
+
|
524
|
+
PreloaderCrashed(const IOException &e)
|
525
|
+
: systemException(NULL),
|
526
|
+
ioException(new IOException(e))
|
527
|
+
{ }
|
528
|
+
|
529
|
+
~PreloaderCrashed() {
|
530
|
+
delete systemException;
|
531
|
+
delete ioException;
|
532
|
+
}
|
533
|
+
|
534
|
+
const oxt::tracable_exception &getException() const {
|
535
|
+
if (systemException != NULL) {
|
536
|
+
return *systemException;
|
400
537
|
} else {
|
401
|
-
|
402
|
-
"the preloader. There was an I/O error while "
|
403
|
-
"sending the startup request message to it: " +
|
404
|
-
e.sys(),
|
405
|
-
SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
|
406
|
-
details);
|
538
|
+
return *ioException;
|
407
539
|
}
|
408
|
-
} catch (const TimeoutException &) {
|
409
|
-
throwPreloaderSpawnException("An error occurred while starting up the "
|
410
|
-
"preloader: it did not read the startup request message in time.",
|
411
|
-
SpawnException::PRELOADER_STARTUP_TIMEOUT,
|
412
|
-
details);
|
413
540
|
}
|
414
|
-
}
|
541
|
+
};
|
415
542
|
|
416
|
-
|
543
|
+
ForkResult invokeForkCommand(HandshakeSession &session, JourneyStep &stepToMarkAsErrored) {
|
417
544
|
TRACE_POINT();
|
418
|
-
string socketAddress;
|
419
545
|
|
420
|
-
|
421
|
-
|
546
|
+
P_ASSERT_EQ(session.journey.getStepInfo(SPAWNING_KIT_PREPARATION).state,
|
547
|
+
STEP_PERFORMED);
|
548
|
+
|
549
|
+
try {
|
550
|
+
StdChannelsAsyncOpenStatePtr stdChannelsAsyncOpenState =
|
551
|
+
openStdChannelsFifosAsynchronously(session);
|
552
|
+
return internalInvokeForkCommand(session, stdChannelsAsyncOpenState,
|
553
|
+
stepToMarkAsErrored);
|
554
|
+
} catch (const PreloaderCrashed &crashException1) {
|
555
|
+
UPDATE_TRACE_POINT();
|
556
|
+
P_WARN("An error occurred while spawning an application process: "
|
557
|
+
<< crashException1.getException().what());
|
558
|
+
P_WARN("The application preloader seems to have crashed,"
|
559
|
+
" restarting it and trying again...");
|
560
|
+
|
561
|
+
session.journey.reset();
|
422
562
|
|
423
563
|
try {
|
424
|
-
|
425
|
-
} catch (const
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
"
|
435
|
-
"
|
436
|
-
|
437
|
-
|
564
|
+
stopPreloader();
|
565
|
+
} catch (const SpawnException &) {
|
566
|
+
throw;
|
567
|
+
} catch (const std::exception &originalException) {
|
568
|
+
session.journey.setStepErrored(SPAWNING_KIT_PREPARATION, true);
|
569
|
+
|
570
|
+
SpawnException e(originalException, session.journey, session.config);
|
571
|
+
e.setSummary(StaticString("Error stopping a crashed preloader: ")
|
572
|
+
+ originalException.what());
|
573
|
+
e.setProblemDescriptionHTML(
|
574
|
+
"<p>The " PROGRAM_NAME " application server tried"
|
575
|
+
" to start the web application by communicating with a"
|
576
|
+
" helper process that we call a \"preloader\". However,"
|
577
|
+
" this helper process crashed unexpectedly. "
|
578
|
+
SHORT_PROGRAM_NAME " then tried to restart it, but"
|
579
|
+
" encountered the following error while trying to"
|
580
|
+
" stop the preloader:</p>"
|
581
|
+
"<pre>" + escapeHTML(originalException.what()) + "</pre>");
|
582
|
+
throw e.finalize();
|
438
583
|
}
|
439
584
|
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
|
445
|
-
details);
|
446
|
-
} else if (line[line.size() - 1] != '\n') {
|
447
|
-
throwPreloaderSpawnException("An error occurred while starting up "
|
448
|
-
"the preloader. It sent a line without a newline character "
|
449
|
-
"in its startup response.",
|
450
|
-
SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
|
451
|
-
details);
|
452
|
-
} else if (line == "\n") {
|
453
|
-
break;
|
454
|
-
}
|
585
|
+
UPDATE_TRACE_POINT();
|
586
|
+
startPreloader();
|
587
|
+
session.journey.reset();
|
588
|
+
session.journey.setStepPerformed(SPAWNING_KIT_PREPARATION, true);
|
455
589
|
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
590
|
+
UPDATE_TRACE_POINT();
|
591
|
+
try {
|
592
|
+
StdChannelsAsyncOpenStatePtr stdChannelsAsyncOpenState =
|
593
|
+
openStdChannelsFifosAsynchronously(session);
|
594
|
+
return internalInvokeForkCommand(session, stdChannelsAsyncOpenState,
|
595
|
+
stepToMarkAsErrored);
|
596
|
+
} catch (const PreloaderCrashed &crashException2) {
|
597
|
+
UPDATE_TRACE_POINT();
|
464
598
|
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
599
|
+
session.journey.reset();
|
600
|
+
session.journey.setStepErrored(SPAWNING_KIT_PREPARATION, true);
|
601
|
+
|
602
|
+
try {
|
603
|
+
stopPreloader();
|
604
|
+
} catch (const SpawnException &) {
|
605
|
+
throw;
|
606
|
+
} catch (const std::exception &originalException) {
|
607
|
+
SpawnException e(originalException, session.journey, session.config);
|
608
|
+
e.setSummary(StaticString("Error stopping a crashed preloader: ")
|
609
|
+
+ originalException.what());
|
610
|
+
e.setProblemDescriptionHTML(
|
611
|
+
"<p>The " PROGRAM_NAME " application server tried"
|
612
|
+
" to start the web application by communicating with a"
|
613
|
+
" helper process that we call a \"preloader\". However,"
|
614
|
+
" this helper process crashed unexpectedly. "
|
615
|
+
SHORT_PROGRAM_NAME " then tried to restart it, but"
|
616
|
+
" encountered the following error while trying to"
|
617
|
+
" stop the preloader:</p>"
|
618
|
+
"<pre>" + escapeHTML(originalException.what()) + "</pre>");
|
619
|
+
throw e.finalize();
|
620
|
+
}
|
621
|
+
|
622
|
+
SpawnException e(crashException2.getException(),
|
623
|
+
session.journey, session.config);
|
624
|
+
e.setSummary(StaticString("An application preloader crashed: ") +
|
625
|
+
crashException2.getException().what());
|
626
|
+
e.setProblemDescriptionHTML(
|
627
|
+
"<p>The " PROGRAM_NAME " application server tried"
|
628
|
+
" to start the web application by communicating with a"
|
629
|
+
" helper process that we call a \"preloader\". However,"
|
630
|
+
" this helper process crashed unexpectedly:</p>"
|
631
|
+
"<pre>" + escapeHTML(crashException2.getException().what())
|
632
|
+
+ "</pre>");
|
633
|
+
throw e.finalize();
|
476
634
|
}
|
477
635
|
}
|
636
|
+
}
|
637
|
+
|
638
|
+
ForkResult internalInvokeForkCommand(HandshakeSession &session,
|
639
|
+
const StdChannelsAsyncOpenStatePtr &stdChannelsAsyncOpenState,
|
640
|
+
JourneyStep &stepToMarkAsErrored)
|
641
|
+
{
|
642
|
+
TRACE_POINT();
|
643
|
+
|
644
|
+
P_ASSERT_EQ(session.journey.getStepInfo(SPAWNING_KIT_PREPARATION).state,
|
645
|
+
STEP_PERFORMED);
|
478
646
|
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
647
|
+
session.journey.setStepInProgress(SPAWNING_KIT_CONNECT_TO_PRELOADER);
|
648
|
+
stepToMarkAsErrored = SPAWNING_KIT_CONNECT_TO_PRELOADER;
|
649
|
+
FileDescriptor fd;
|
650
|
+
string line;
|
651
|
+
Json::Value doc;
|
652
|
+
try {
|
653
|
+
fd = connectToPreloader(session);
|
654
|
+
} catch (const SystemException &e) {
|
655
|
+
throw PreloaderCrashed(e);
|
656
|
+
} catch (const IOException &e) {
|
657
|
+
throw PreloaderCrashed(e);
|
658
|
+
}
|
659
|
+
|
660
|
+
session.journey.setStepPerformed(SPAWNING_KIT_CONNECT_TO_PRELOADER);
|
661
|
+
session.journey.setStepInProgress(SPAWNING_KIT_SEND_COMMAND_TO_PRELOADER);
|
662
|
+
stepToMarkAsErrored = SPAWNING_KIT_SEND_COMMAND_TO_PRELOADER;
|
663
|
+
try {
|
664
|
+
sendForkCommand(session, fd);
|
665
|
+
} catch (const SystemException &e) {
|
666
|
+
throw PreloaderCrashed(e);
|
667
|
+
} catch (const IOException &e) {
|
668
|
+
throw PreloaderCrashed(e);
|
485
669
|
}
|
486
670
|
|
487
|
-
|
671
|
+
session.journey.setStepPerformed(SPAWNING_KIT_SEND_COMMAND_TO_PRELOADER);
|
672
|
+
session.journey.setStepInProgress(SPAWNING_KIT_READ_RESPONSE_FROM_PRELOADER);
|
673
|
+
stepToMarkAsErrored = SPAWNING_KIT_READ_RESPONSE_FROM_PRELOADER;
|
674
|
+
try {
|
675
|
+
line = readForkCommandResponse(session, fd);
|
676
|
+
} catch (const SystemException &e) {
|
677
|
+
throw PreloaderCrashed(e);
|
678
|
+
} catch (const IOException &e) {
|
679
|
+
throw PreloaderCrashed(e);
|
680
|
+
}
|
681
|
+
|
682
|
+
session.journey.setStepPerformed(SPAWNING_KIT_READ_RESPONSE_FROM_PRELOADER);
|
683
|
+
session.journey.setStepInProgress(SPAWNING_KIT_PARSE_RESPONSE_FROM_PRELOADER);
|
684
|
+
stepToMarkAsErrored = SPAWNING_KIT_PARSE_RESPONSE_FROM_PRELOADER;
|
685
|
+
doc = parseForkCommandResponse(session, line);
|
686
|
+
|
687
|
+
session.journey.setStepPerformed(SPAWNING_KIT_PARSE_RESPONSE_FROM_PRELOADER);
|
688
|
+
session.journey.setStepInProgress(SPAWNING_KIT_PROCESS_RESPONSE_FROM_PRELOADER);
|
689
|
+
stepToMarkAsErrored = SPAWNING_KIT_PROCESS_RESPONSE_FROM_PRELOADER;
|
690
|
+
return handleForkCommandResponse(session, stdChannelsAsyncOpenState, doc);
|
488
691
|
}
|
489
692
|
|
490
|
-
void
|
693
|
+
void sendForkCommand(HandshakeSession &session, const FileDescriptor &fd) {
|
491
694
|
TRACE_POINT();
|
492
|
-
|
695
|
+
Json::Value doc;
|
493
696
|
|
494
|
-
|
495
|
-
|
697
|
+
doc["command"] = "spawn";
|
698
|
+
doc["work_dir"] = session.workDir->getPath();
|
496
699
|
|
497
|
-
|
498
|
-
|
499
|
-
} catch (const SystemException &e) {
|
500
|
-
throwPreloaderSpawnException("An error occurred while starting up "
|
501
|
-
"the preloader. There was an I/O error while reading its "
|
502
|
-
"startup response: " + e.sys(),
|
503
|
-
SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
|
504
|
-
details);
|
505
|
-
} catch (const TimeoutException &) {
|
506
|
-
throwPreloaderSpawnException("An error occurred while starting up "
|
507
|
-
"the preloader: it did not write a startup response in time. "
|
508
|
-
"If your app needs more time to start you can increase the "
|
509
|
-
"Passenger start timeout config option.",
|
510
|
-
SpawnException::PRELOADER_STARTUP_TIMEOUT,
|
511
|
-
details);
|
512
|
-
}
|
700
|
+
writeExact(fd, Json::FastWriter().write(doc), &session.timeoutUsec);
|
701
|
+
}
|
513
702
|
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
"sending its startup response.",
|
518
|
-
SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
|
519
|
-
details);
|
520
|
-
} else if (line[line.size() - 1] != '\n') {
|
521
|
-
throwPreloaderSpawnException("An error occurred while starting up "
|
522
|
-
"the preloader. It sent a line without a newline character "
|
523
|
-
"in its startup response.",
|
524
|
-
SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
|
525
|
-
details);
|
526
|
-
} else if (line == "\n") {
|
527
|
-
break;
|
528
|
-
}
|
703
|
+
string readForkCommandResponse(HandshakeSession &session, const FileDescriptor &fd) {
|
704
|
+
TRACE_POINT();
|
705
|
+
BufferedIO io(fd);
|
529
706
|
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
707
|
+
try {
|
708
|
+
return io.readLine(10240, &session.timeoutUsec);
|
709
|
+
} catch (const SecurityException &) {
|
710
|
+
session.journey.setStepErrored(SPAWNING_KIT_READ_RESPONSE_FROM_PRELOADER);
|
711
|
+
|
712
|
+
SpawnException e(INTERNAL_ERROR, session.journey, session.config);
|
713
|
+
addPreloaderEnvDumps(e);
|
714
|
+
e.setSummary("The preloader process sent a response that exceeds the maximum size limit.");
|
715
|
+
e.setProblemDescriptionHTML(
|
716
|
+
"<p>The " PROGRAM_NAME " application server tried"
|
717
|
+
" to start the web application by communicating with a"
|
718
|
+
" helper process that we call a \"preloader\". However,"
|
719
|
+
" this helper process sent a response that exceeded the"
|
720
|
+
" internally-defined maximum size limit.</p>");
|
721
|
+
e.setSolutionDescriptionHTML(
|
722
|
+
"<p class=\"sole-solution\">"
|
723
|
+
"This is probably a bug in the preloader process. Please "
|
724
|
+
"<a href=\"" SUPPORT_URL "\">"
|
725
|
+
"report this bug</a>."
|
726
|
+
"</p>");
|
727
|
+
throw e.finalize();
|
728
|
+
}
|
729
|
+
}
|
538
730
|
|
539
|
-
|
540
|
-
|
541
|
-
|
731
|
+
Json::Value parseForkCommandResponse(HandshakeSession &session, const string &data) {
|
732
|
+
TRACE_POINT();
|
733
|
+
Json::Value doc;
|
734
|
+
Json::Reader reader;
|
735
|
+
|
736
|
+
if (!reader.parse(data, doc)) {
|
737
|
+
session.journey.setStepErrored(SPAWNING_KIT_PARSE_RESPONSE_FROM_PRELOADER);
|
738
|
+
|
739
|
+
SpawnException e(INTERNAL_ERROR, session.journey, session.config);
|
740
|
+
addPreloaderEnvDumps(e);
|
741
|
+
e.setSummary("The preloader process sent an unparseable response: " + data);
|
742
|
+
e.setProblemDescriptionHTML(
|
743
|
+
"<p>The " PROGRAM_NAME " application server tried"
|
744
|
+
" to start the web application by communicating with a"
|
745
|
+
" helper process that we call a \"preloader\". However,"
|
746
|
+
" this helper process sent a response that looks like"
|
747
|
+
" gibberish.</p>"
|
748
|
+
"<p>The response is as follows:</p>"
|
749
|
+
"<pre>" + escapeHTML(data) + "</pre>");
|
750
|
+
e.setSolutionDescriptionHTML(
|
751
|
+
"<p class=\"sole-solution\">"
|
752
|
+
"This is probably a bug in the preloader process. Please "
|
753
|
+
"<a href=\"" SUPPORT_URL "\">"
|
754
|
+
"report this bug</a>."
|
755
|
+
"</p>");
|
756
|
+
throw e.finalize();
|
542
757
|
}
|
543
758
|
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
e.
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
"
|
556
|
-
"
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
"
|
563
|
-
"
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
}
|
568
|
-
|
569
|
-
void handleInvalidResponseType(StartupDetails &details, const string &line) {
|
570
|
-
if (line.empty()) {
|
571
|
-
throwPreloaderSpawnException("An error occurred while starting up "
|
572
|
-
"the preloader. It exited before signalling successful "
|
573
|
-
"startup back to " PROGRAM_NAME ".",
|
574
|
-
SpawnException::PRELOADER_STARTUP_ERROR,
|
575
|
-
details);
|
576
|
-
} else {
|
577
|
-
throwPreloaderSpawnException("An error occurred while starting up "
|
578
|
-
"the preloader. It sent an unknown response type \"" +
|
579
|
-
cEscapeString(line) + "\".",
|
580
|
-
SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
|
581
|
-
details);
|
759
|
+
UPDATE_TRACE_POINT();
|
760
|
+
if (!validateForkCommandResponse(doc)) {
|
761
|
+
session.journey.setStepErrored(SPAWNING_KIT_PARSE_RESPONSE_FROM_PRELOADER);
|
762
|
+
|
763
|
+
SpawnException e(INTERNAL_ERROR, session.journey, session.config);
|
764
|
+
addPreloaderEnvDumps(e);
|
765
|
+
e.setSummary("The preloader process sent a response that does not"
|
766
|
+
" match the expected structure: " + stringifyJson(doc));
|
767
|
+
e.setProblemDescriptionHTML(
|
768
|
+
"<p>The " PROGRAM_NAME " application server tried"
|
769
|
+
" to start the web application by communicating with a"
|
770
|
+
" helper process that we call a \"preloader\". However,"
|
771
|
+
" this helper process sent a response that does not match"
|
772
|
+
" the structure that " SHORT_PROGRAM_NAME " expects.</p>"
|
773
|
+
"<p>The response is as follows:</p>"
|
774
|
+
"<pre>" + escapeHTML(doc.toStyledString()) + "</pre>");
|
775
|
+
e.setSolutionDescriptionHTML(
|
776
|
+
"<p class=\"sole-solution\">"
|
777
|
+
"This is probably a bug in the preloader process. Please "
|
778
|
+
"<a href=\"" SUPPORT_URL "\">"
|
779
|
+
"report this bug</a>."
|
780
|
+
"</p>");
|
781
|
+
throw e.finalize();
|
582
782
|
}
|
783
|
+
|
784
|
+
return doc;
|
583
785
|
}
|
584
786
|
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
details);
|
596
|
-
} catch (const TimeoutException &) {
|
597
|
-
throwPreloaderSpawnException("An error occurred while starting up "
|
598
|
-
"the preloader: it did not write a handshake message in time.",
|
599
|
-
SpawnException::PRELOADER_STARTUP_TIMEOUT,
|
600
|
-
details);
|
601
|
-
}
|
602
|
-
|
603
|
-
if (result == "I have control 1.0\n") {
|
604
|
-
UPDATE_TRACE_POINT();
|
605
|
-
sendStartupRequest(details);
|
606
|
-
try {
|
607
|
-
result = readMessageLine(details);
|
608
|
-
} catch (const SystemException &e) {
|
609
|
-
throwPreloaderSpawnException("An error occurred while starting up "
|
610
|
-
"the preloader. There was an I/O error while reading its "
|
611
|
-
"startup response: " + e.sys(),
|
612
|
-
SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
|
613
|
-
details);
|
614
|
-
} catch (const TimeoutException &) {
|
615
|
-
throwPreloaderSpawnException("An error occurred while starting up "
|
616
|
-
"the preloader: it did not write a startup response in time. "
|
617
|
-
"If your app needs more time to start you can increase the "
|
618
|
-
"Passenger start timeout config option.",
|
619
|
-
SpawnException::PRELOADER_STARTUP_TIMEOUT,
|
620
|
-
details);
|
787
|
+
bool validateForkCommandResponse(const Json::Value &doc) const {
|
788
|
+
if (!doc.isObject()) {
|
789
|
+
return false;
|
790
|
+
}
|
791
|
+
if (!doc.isMember("result") || !doc["result"].isString()) {
|
792
|
+
return false;
|
793
|
+
}
|
794
|
+
if (doc["result"].asString() == "ok") {
|
795
|
+
if (!doc.isMember("pid") || !doc["pid"].isInt()) {
|
796
|
+
return false;
|
621
797
|
}
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
} else {
|
627
|
-
handleInvalidResponseType(details, result);
|
798
|
+
return true;
|
799
|
+
} else if (doc["result"].asString() == "error") {
|
800
|
+
if (!doc.isMember("message") || !doc["message"].isString()) {
|
801
|
+
return false;
|
628
802
|
}
|
803
|
+
return true;
|
629
804
|
} else {
|
805
|
+
return false;
|
806
|
+
}
|
807
|
+
}
|
808
|
+
|
809
|
+
ForkResult handleForkCommandResponse(HandshakeSession &session,
|
810
|
+
const StdChannelsAsyncOpenStatePtr &stdChannelsAsyncOpenState,
|
811
|
+
const Json::Value &doc)
|
812
|
+
{
|
813
|
+
TRACE_POINT();
|
814
|
+
if (doc["result"].asString() == "ok") {
|
815
|
+
return handleForkCommandResponseSuccess(session, stdChannelsAsyncOpenState,
|
816
|
+
doc);
|
817
|
+
} else {
|
818
|
+
P_ASSERT_EQ(doc["result"].asString(), "error");
|
819
|
+
return handleForkCommandResponseError(session, doc);
|
820
|
+
}
|
821
|
+
}
|
822
|
+
|
823
|
+
ForkResult handleForkCommandResponseSuccess(HandshakeSession &session,
|
824
|
+
const StdChannelsAsyncOpenStatePtr &stdChannelsAsyncOpenState, const Json::Value &doc)
|
825
|
+
{
|
826
|
+
TRACE_POINT();
|
827
|
+
pid_t spawnedPid = doc["pid"].asInt();
|
828
|
+
ScopeGuard guard(boost::bind(nonInterruptableKillAndWaitpid, spawnedPid));
|
829
|
+
|
830
|
+
UPDATE_TRACE_POINT();
|
831
|
+
waitForStdChannelFifosToBeOpenedByPeer(stdChannelsAsyncOpenState,
|
832
|
+
session, spawnedPid);
|
833
|
+
|
834
|
+
UPDATE_TRACE_POINT();
|
835
|
+
// How do we know the preloader actually forked a process
|
836
|
+
// instead of reporting the PID of a random other existing process?
|
837
|
+
// For security reasons we perform a UID check.
|
838
|
+
uid_t spawnedUid = getProcessUid(session, spawnedPid,
|
839
|
+
stdChannelsAsyncOpenState->stdoutAndErrCapturer);
|
840
|
+
if (spawnedUid != session.uid) {
|
630
841
|
UPDATE_TRACE_POINT();
|
631
|
-
|
632
|
-
|
842
|
+
session.journey.setStepErrored(SPAWNING_KIT_PROCESS_RESPONSE_FROM_PRELOADER);
|
843
|
+
|
844
|
+
SpawnException e(INTERNAL_ERROR, session.journey, session.config);
|
845
|
+
addPreloaderEnvDumps(e);
|
846
|
+
e.setSummary("The process that the preloader said it spawned, PID "
|
847
|
+
+ toString(spawnedPid) + ", has UID " + toString(spawnedUid)
|
848
|
+
+ ", but the expected UID is " + toString(session.uid));
|
849
|
+
e.setSubprocessPid(spawnedPid);
|
850
|
+
e.setStdoutAndErrData(getBackgroundIOCapturerData(
|
851
|
+
stdChannelsAsyncOpenState->stdoutAndErrCapturer));
|
852
|
+
e.setProblemDescriptionHTML(
|
853
|
+
"<h2>Application process has unexpected UID</h2>"
|
854
|
+
"<p>The " PROGRAM_NAME " application server tried"
|
855
|
+
" to start the web application by communicating with a"
|
856
|
+
" helper process that we call a \"preloader\". However,"
|
857
|
+
" the web application process that the preloader started"
|
858
|
+
" belongs to the wrong user. The UID of the web"
|
859
|
+
" application process should be " + toString(session.uid)
|
860
|
+
+ ", but is actually " + toString(session.uid) + ".</p>");
|
861
|
+
if (!session.config->genericApp && session.config->startsUsingWrapper
|
862
|
+
&& session.config->wrapperSuppliedByThirdParty)
|
863
|
+
{
|
864
|
+
e.setSolutionDescriptionHTML(
|
865
|
+
"<h2>Please report this bug</h2>"
|
866
|
+
"<p class=\"sole-solution\">"
|
867
|
+
"This is probably a bug in the preloader process. The preloader "
|
868
|
+
"wrapper program is not written by the " PROGRAM_NAME " authors, "
|
869
|
+
"but by a third party. Please report this bug to the author of "
|
870
|
+
"the preloader wrapper program."
|
871
|
+
"</p>");
|
633
872
|
} else {
|
634
|
-
|
873
|
+
e.setSolutionDescriptionHTML(
|
874
|
+
"<h2>Please report this bug</h2>"
|
875
|
+
"<p class=\"sole-solution\">"
|
876
|
+
"This is probably a bug in the preloader process. The preloader "
|
877
|
+
"is an internal tool part of " PROGRAM_NAME ". Please "
|
878
|
+
"<a href=\"" SUPPORT_URL "\">"
|
879
|
+
"report this bug</a>."
|
880
|
+
"</p>");
|
635
881
|
}
|
882
|
+
throw e.finalize();
|
636
883
|
}
|
637
884
|
|
638
|
-
|
639
|
-
|
640
|
-
|
885
|
+
UPDATE_TRACE_POINT();
|
886
|
+
string alreadyReadStdoutAndErrData;
|
887
|
+
if (stdChannelsAsyncOpenState->stdoutAndErrCapturer != NULL) {
|
888
|
+
stdChannelsAsyncOpenState->stdoutAndErrCapturer->stop();
|
889
|
+
alreadyReadStdoutAndErrData = stdChannelsAsyncOpenState->stdoutAndErrCapturer->getData();
|
890
|
+
}
|
891
|
+
guard.clear();
|
892
|
+
return ForkResult(spawnedPid, stdChannelsAsyncOpenState->stdinFd,
|
893
|
+
stdChannelsAsyncOpenState->stdoutAndErrFd,
|
894
|
+
alreadyReadStdoutAndErrData);
|
641
895
|
}
|
642
896
|
|
643
|
-
|
644
|
-
|
645
|
-
|
897
|
+
ForkResult handleForkCommandResponseError(HandshakeSession &session,
|
898
|
+
const Json::Value &doc)
|
899
|
+
{
|
900
|
+
session.journey.setStepErrored(SPAWNING_KIT_PROCESS_RESPONSE_FROM_PRELOADER);
|
901
|
+
|
902
|
+
SpawnException e(INTERNAL_ERROR, session.journey, session.config);
|
903
|
+
addPreloaderEnvDumps(e);
|
904
|
+
e.setSummary("An error occured while starting the web application: "
|
905
|
+
+ doc["message"].asString());
|
906
|
+
e.setProblemDescriptionHTML(
|
907
|
+
"<p>The " PROGRAM_NAME " application server tried to"
|
908
|
+
" start the web application by communicating with a"
|
909
|
+
" helper process that we call a \"preloader\". However, "
|
910
|
+
" this helper process reported an error:</p>"
|
911
|
+
"<pre>" + escapeHTML(doc["message"].asString()) + "</pre>");
|
912
|
+
e.setSolutionDescriptionHTML(
|
913
|
+
"<p class=\"sole-solution\">"
|
914
|
+
"Please try troubleshooting the problem by studying the"
|
915
|
+
" <strong>error message</strong> and the"
|
916
|
+
" <strong>diagnostics</strong> reports. You can also"
|
917
|
+
" consult <a href=\"" SUPPORT_URL "\">the " SHORT_PROGRAM_NAME
|
918
|
+
" support resources</a> for help.</p>");
|
919
|
+
throw e.finalize();
|
920
|
+
}
|
646
921
|
|
647
|
-
|
648
|
-
|
922
|
+
void createStdChannelFifos(const HandshakeSession &session) {
|
923
|
+
const string &workDir = session.workDir->getPath();
|
924
|
+
createFifo(session, workDir + "/stdin");
|
925
|
+
createFifo(session, workDir + "/stdout_and_err");
|
926
|
+
}
|
649
927
|
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
928
|
+
void createFifo(const HandshakeSession &session, const string &path) {
|
929
|
+
int ret;
|
930
|
+
|
931
|
+
do {
|
932
|
+
ret = mkfifo(path.c_str(), 0600);
|
933
|
+
} while (ret == -1 && errno == EAGAIN);
|
934
|
+
if (ret == -1) {
|
935
|
+
int e = errno;
|
936
|
+
throw FileSystemException("Cannot create FIFO file " + path,
|
937
|
+
e, path);
|
658
938
|
}
|
659
939
|
|
660
|
-
|
940
|
+
ret = syscalls::chown(path.c_str(), session.uid, session.gid);
|
941
|
+
if (ret == -1) {
|
942
|
+
int e = errno;
|
943
|
+
throw FileSystemException("Cannot change owner and group on FIFO file " + path,
|
944
|
+
e, path);
|
945
|
+
}
|
661
946
|
}
|
662
947
|
|
663
|
-
|
948
|
+
string getBackgroundIOCapturerData(const BackgroundIOCapturerPtr &capturer) const {
|
949
|
+
if (capturer != NULL) {
|
950
|
+
// Sleep shortly to allow the child process to finish writing logs.
|
951
|
+
syscalls::usleep(50000);
|
952
|
+
return capturer->getData();
|
953
|
+
} else {
|
954
|
+
return string();
|
955
|
+
}
|
956
|
+
}
|
957
|
+
|
958
|
+
uid_t getProcessUid(HandshakeSession &session, pid_t pid,
|
959
|
+
const BackgroundIOCapturerPtr &stdoutAndErrCapturer)
|
960
|
+
{
|
664
961
|
TRACE_POINT();
|
665
|
-
|
666
|
-
FileDescriptor fd;
|
962
|
+
uid_t uid = (uid_t) -1;
|
667
963
|
|
668
964
|
try {
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
965
|
+
vector<pid_t> pids;
|
966
|
+
pids.push_back(pid);
|
967
|
+
ProcessMetricMap result = ProcessMetricsCollector().collect(pids);
|
968
|
+
uid = result[pid].uid;
|
969
|
+
} catch (const ParseException &) {
|
970
|
+
HandshakePerform::loadJourneyStateFromResponseDir(session, pid, stdoutAndErrCapturer);
|
971
|
+
session.journey.setStepErrored(SPAWNING_KIT_PROCESS_RESPONSE_FROM_PRELOADER);
|
972
|
+
|
973
|
+
SpawnException e(INTERNAL_ERROR, session.journey, session.config);
|
974
|
+
addPreloaderEnvDumps(e);
|
975
|
+
e.setSummary("Unable to query the UID of spawned application process "
|
976
|
+
+ toString(pid) + ": error parsing 'ps' output");
|
977
|
+
e.setSubprocessPid(pid);
|
978
|
+
e.setProblemDescriptionHTML(
|
979
|
+
"<h2>Unable to use 'ps' to query PID " + toString(pid) + "</h2>"
|
980
|
+
"<p>The " PROGRAM_NAME " application server tried"
|
981
|
+
" to start the web application. As part of the starting"
|
982
|
+
" procedure, " SHORT_PROGRAM_NAME " also tried to query"
|
983
|
+
" the system user ID of the web application process"
|
984
|
+
" using the operating system's \"ps\" tool. However,"
|
985
|
+
" this tool returned output that " SHORT_PROGRAM_NAME
|
986
|
+
" could not understand.</p>");
|
987
|
+
e.setSolutionDescriptionHTML(
|
988
|
+
createSolutionDescriptionForProcessMetricsCollectionError());
|
989
|
+
throw e.finalize();
|
990
|
+
} catch (const SystemException &originalException) {
|
991
|
+
HandshakePerform::loadJourneyStateFromResponseDir(session, pid, stdoutAndErrCapturer);
|
992
|
+
session.journey.setStepErrored(SPAWNING_KIT_PROCESS_RESPONSE_FROM_PRELOADER);
|
993
|
+
|
994
|
+
SpawnException e(OPERATING_SYSTEM_ERROR, session.journey, session.config);
|
995
|
+
addPreloaderEnvDumps(e);
|
996
|
+
e.setSummary("Unable to query the UID of spawned application process "
|
997
|
+
+ toString(pid) + "; error capturing 'ps' output: "
|
998
|
+
+ originalException.what());
|
999
|
+
e.setSubprocessPid(pid);
|
1000
|
+
e.setProblemDescriptionHTML(
|
1001
|
+
"<h2>Error capturing 'ps' output for PID " + toString(pid) + "</h2>"
|
1002
|
+
"<p>The " PROGRAM_NAME " application server tried"
|
1003
|
+
" to start the web application. As part of the starting"
|
1004
|
+
" procedure, " SHORT_PROGRAM_NAME " also tried to query"
|
1005
|
+
" the system user ID of the web application process."
|
1006
|
+
" This is done by using the operating system's \"ps\""
|
1007
|
+
" tool and by querying operating system APIs and special"
|
1008
|
+
" files. However, an error was encountered while doing"
|
1009
|
+
" one of those things.</p>"
|
1010
|
+
"<p>The error returned by the operating system is as follows:</p>"
|
1011
|
+
"<pre>" + escapeHTML(originalException.what()) + "</pre>");
|
1012
|
+
e.setSolutionDescriptionHTML(
|
1013
|
+
createSolutionDescriptionForProcessMetricsCollectionError());
|
1014
|
+
throw e.finalize();
|
679
1015
|
}
|
680
|
-
P_LOG_FILE_DESCRIPTOR_PURPOSE(fd, "Preloader " << pid
|
681
|
-
<< " (" << options.appRoot << ") connection");
|
682
1016
|
|
683
1017
|
UPDATE_TRACE_POINT();
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
1018
|
+
if (uid == (uid_t) -1) {
|
1019
|
+
if (osProcessExists(pid)) {
|
1020
|
+
HandshakePerform::loadJourneyStateFromResponseDir(session, pid, stdoutAndErrCapturer);
|
1021
|
+
session.journey.setStepErrored(SPAWNING_KIT_PROCESS_RESPONSE_FROM_PRELOADER);
|
1022
|
+
|
1023
|
+
SpawnException e(INTERNAL_ERROR, session.journey, session.config);
|
1024
|
+
addPreloaderEnvDumps(e);
|
1025
|
+
e.setSummary("Unable to query the UID of spawned application process "
|
1026
|
+
+ toString(pid) + ": 'ps' did not report information"
|
1027
|
+
" about this process");
|
1028
|
+
e.setSubprocessPid(pid);
|
1029
|
+
e.setProblemDescriptionHTML(
|
1030
|
+
"<h2>'ps' did not return any information about PID " + toString(pid) + "</h2>"
|
1031
|
+
"<p>The " PROGRAM_NAME " application server tried"
|
1032
|
+
" to start the web application. As part of the starting"
|
1033
|
+
" procedure, " SHORT_PROGRAM_NAME " also tried to query"
|
1034
|
+
" the system user ID of the web application process"
|
1035
|
+
" using the operating system's \"ps\" tool. However,"
|
1036
|
+
" this tool did not return any information about"
|
1037
|
+
" the web application process.</p>");
|
1038
|
+
e.setSolutionDescriptionHTML(
|
1039
|
+
createSolutionDescriptionForProcessMetricsCollectionError());
|
1040
|
+
throw e.finalize();
|
1041
|
+
} else {
|
1042
|
+
HandshakePerform::loadJourneyStateFromResponseDir(session, pid, stdoutAndErrCapturer);
|
1043
|
+
session.journey.setStepErrored(SPAWNING_KIT_PROCESS_RESPONSE_FROM_PRELOADER);
|
1044
|
+
|
1045
|
+
SpawnException e(INTERNAL_ERROR, session.journey, session.config);
|
1046
|
+
addPreloaderEnvDumps(e);
|
1047
|
+
e.setSummary("The application process spawned from the preloader"
|
1048
|
+
" seems to have exited prematurely");
|
1049
|
+
e.setSubprocessPid(pid);
|
1050
|
+
e.setStdoutAndErrData(getBackgroundIOCapturerData(stdoutAndErrCapturer));
|
1051
|
+
e.setProblemDescriptionHTML(
|
1052
|
+
"<h2>Application process exited prematurely</h2>"
|
1053
|
+
"<p>The " PROGRAM_NAME " application server tried"
|
1054
|
+
" to start the web application. As part of the starting"
|
1055
|
+
" procedure, " SHORT_PROGRAM_NAME " also tried to query"
|
1056
|
+
" the system user ID of the web application process"
|
1057
|
+
" using the operating system's \"ps\" tool. However,"
|
1058
|
+
" this tool did not return any information about"
|
1059
|
+
" the web application process.</p>");
|
1060
|
+
e.setSolutionDescriptionHTML(
|
1061
|
+
createSolutionDescriptionForProcessMetricsCollectionError());
|
1062
|
+
throw e.finalize();
|
1063
|
+
}
|
1064
|
+
} else {
|
1065
|
+
return uid;
|
697
1066
|
}
|
698
|
-
|
1067
|
+
}
|
699
1068
|
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
1069
|
+
static string createSolutionDescriptionForProcessMetricsCollectionError() {
|
1070
|
+
const char *path = getenv("PATH");
|
1071
|
+
if (path == NULL || path[0] == '\0') {
|
1072
|
+
path = "(empty)";
|
1073
|
+
}
|
1074
|
+
return "<div class=\"multiple-solutions\">"
|
1075
|
+
|
1076
|
+
"<h3>Check whether the \"ps\" tool is installed and accessible by "
|
1077
|
+
SHORT_PROGRAM_NAME "</h3>"
|
1078
|
+
"<p>Maybe \"ps\" is not installed. Or maybe it is installed, but "
|
1079
|
+
SHORT_PROGRAM_NAME " cannot find it inside its PATH. Or"
|
1080
|
+
" maybe filesystem permissions disallow " SHORT_PROGRAM_NAME
|
1081
|
+
" from accessing \"ps\". Please check all these factors and"
|
1082
|
+
" fix them if necessary.</p>"
|
1083
|
+
"<p>" SHORT_PROGRAM_NAME "'s PATH is:</p>"
|
1084
|
+
"<pre>" + escapeHTML(path) + "</pre>"
|
1085
|
+
|
1086
|
+
"<h3>Check whether the server is low on resources</h3>"
|
1087
|
+
"<p>Maybe the server is currently low on resources. This would"
|
1088
|
+
" cause the \"ps\" tool to encounter errors. Please study the"
|
1089
|
+
" <em>error message</em> and the <em>diagnostics reports</em> to"
|
1090
|
+
" verify whether this is the case. Key things to check for:</p>"
|
1091
|
+
"<ul>"
|
1092
|
+
"<li>Excessive CPU usage</li>"
|
1093
|
+
"<li>Memory and swap</li>"
|
1094
|
+
"<li>Ulimits</li>"
|
1095
|
+
"</ul>"
|
1096
|
+
"<p>If the server is indeed low on resources, find a way to"
|
1097
|
+
" free up some resources.</p>"
|
1098
|
+
|
1099
|
+
"<h3>Check whether /proc is mounted</h3>"
|
1100
|
+
"<p>On many operating systems including Linux and FreeBSD, \"ps\""
|
1101
|
+
" only works if /proc is mounted. Please check this.</p>"
|
1102
|
+
|
1103
|
+
"<h3>Still no luck?</h3>"
|
1104
|
+
"<p>Please try troubleshooting the problem by studying the"
|
1105
|
+
" <em>diagnostics</em> reports.</p>"
|
1106
|
+
|
1107
|
+
"</div>";
|
1108
|
+
}
|
730
1109
|
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
1110
|
+
static void adjustTimeout(MonotonicTimeUsec startTime, unsigned long long *timeout) {
|
1111
|
+
boost::this_thread::disable_interruption di;
|
1112
|
+
boost::this_thread::disable_syscall_interruption dsi;
|
1113
|
+
MonotonicTimeUsec now = SystemTime::getMonotonicUsec();
|
1114
|
+
assert(now >= startTime);
|
1115
|
+
MonotonicTimeUsec diff = now - startTime;
|
1116
|
+
if (*timeout >= diff) {
|
1117
|
+
*timeout -= diff;
|
1118
|
+
} else {
|
1119
|
+
*timeout = 0;
|
1120
|
+
}
|
1121
|
+
}
|
736
1122
|
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
details.timeout = timeout;
|
741
|
-
handleSpawnErrorResponse(details);
|
1123
|
+
static void doClosedir(DIR *dir) {
|
1124
|
+
closedir(dir);
|
1125
|
+
}
|
742
1126
|
|
743
|
-
|
744
|
-
|
745
|
-
|
1127
|
+
static string findPreloaderCommandSocketAddress(const HandshakeSession &session) {
|
1128
|
+
const vector<Result::Socket> &sockets = session.result.sockets;
|
1129
|
+
vector<Result::Socket>::const_iterator it, end = sockets.end();
|
1130
|
+
for (it = sockets.begin(); it != end; it++) {
|
1131
|
+
if (it->protocol == "preloader") {
|
1132
|
+
return it->address;
|
1133
|
+
}
|
746
1134
|
}
|
1135
|
+
return string();
|
747
1136
|
}
|
748
1137
|
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
ScopeGuard guard(boost::bind(
|
757
|
-
|
758
|
-
|
1138
|
+
static StringKeyTable<string> loadAnnotationsFromEnvDumpDir(const string &envDumpDir) {
|
1139
|
+
string path = envDumpDir + "/annotations";
|
1140
|
+
DIR *dir = opendir(path.c_str());
|
1141
|
+
if (dir == NULL) {
|
1142
|
+
return StringKeyTable<string>();
|
1143
|
+
}
|
1144
|
+
|
1145
|
+
ScopeGuard guard(boost::bind(doClosedir, dir));
|
1146
|
+
StringKeyTable<string> result;
|
1147
|
+
struct dirent *ent;
|
1148
|
+
while ((ent = readdir(dir)) != NULL) {
|
1149
|
+
if (ent->d_name[0] != '.') {
|
1150
|
+
result.insert(ent->d_name, strip(
|
1151
|
+
Passenger::readAll(path + "/" + ent->d_name)),
|
1152
|
+
true);
|
1153
|
+
}
|
1154
|
+
}
|
1155
|
+
|
1156
|
+
result.compact();
|
1157
|
+
|
1158
|
+
return result;
|
759
1159
|
}
|
760
1160
|
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
e.
|
1161
|
+
void addPreloaderEnvDumps(SpawnException &e) const {
|
1162
|
+
e.setPreloaderPid(pid);
|
1163
|
+
e.setPreloaderEnvvars(preloaderEnvvars);
|
1164
|
+
e.setPreloaderUserInfo(preloaderUserInfo);
|
1165
|
+
e.setPreloaderUlimits(preloaderUlimits);
|
1166
|
+
|
1167
|
+
if (e.getSubprocessEnvvars().empty()) {
|
1168
|
+
e.setSubprocessEnvvars(preloaderEnvvars);
|
1169
|
+
}
|
1170
|
+
if (e.getSubprocessUserInfo().empty()) {
|
1171
|
+
e.setSubprocessUserInfo(preloaderUserInfo);
|
1172
|
+
}
|
1173
|
+
if (e.getSubprocessUlimits().empty()) {
|
1174
|
+
e.setSubprocessUlimits(preloaderUlimits);
|
1175
|
+
}
|
1176
|
+
|
1177
|
+
StringKeyTable<string>::ConstIterator it(preloaderAnnotations);
|
1178
|
+
while (*it != NULL) {
|
1179
|
+
e.setAnnotation(it.getKey(), it.getValue(), false);
|
1180
|
+
it.next();
|
1181
|
+
}
|
765
1182
|
}
|
766
1183
|
|
767
1184
|
public:
|
768
|
-
SmartSpawner(
|
769
|
-
const
|
770
|
-
const
|
771
|
-
: Spawner(
|
772
|
-
preloaderCommand
|
1185
|
+
SmartSpawner(Context *context,
|
1186
|
+
const vector<string> &preloaderCommand,
|
1187
|
+
const AppPoolOptions &_options)
|
1188
|
+
: Spawner(context),
|
1189
|
+
preloaderCommandString(createCommandString(preloaderCommand))
|
773
1190
|
{
|
774
1191
|
if (preloaderCommand.size() < 2) {
|
775
1192
|
throw ArgumentException("preloaderCommand must have at least 2 elements");
|
776
1193
|
}
|
777
1194
|
|
778
|
-
options = _options.copyAndPersist()
|
1195
|
+
options = _options.copyAndPersist();
|
779
1196
|
pid = -1;
|
780
1197
|
m_lastUsed = SystemTime::getUsec();
|
781
1198
|
}
|
@@ -785,10 +1202,10 @@ public:
|
|
785
1202
|
stopPreloader();
|
786
1203
|
}
|
787
1204
|
|
788
|
-
virtual Result spawn(const
|
1205
|
+
virtual Result spawn(const AppPoolOptions &options) {
|
789
1206
|
TRACE_POINT();
|
790
|
-
|
791
|
-
|
1207
|
+
P_ASSERT_EQ(options.appType, this->options.appType);
|
1208
|
+
P_ASSERT_EQ(options.appRoot, this->options.appRoot);
|
792
1209
|
|
793
1210
|
P_DEBUG("Spawning new process: appRoot=" << options.appRoot);
|
794
1211
|
possiblyRaiseInternalError(options);
|
@@ -805,11 +1222,59 @@ public:
|
|
805
1222
|
}
|
806
1223
|
|
807
1224
|
UPDATE_TRACE_POINT();
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
1225
|
+
Config config;
|
1226
|
+
Json::Value extraArgs;
|
1227
|
+
try {
|
1228
|
+
setConfigFromAppPoolOptions(&config, extraArgs, options);
|
1229
|
+
} catch (const std::exception &originalException) {
|
1230
|
+
Journey journey(SPAWN_THROUGH_PRELOADER, true);
|
1231
|
+
journey.setStepErrored(SPAWNING_KIT_PREPARATION, true);
|
1232
|
+
SpawnException e(originalException, journey, &config);
|
1233
|
+
addPreloaderEnvDumps(e);
|
1234
|
+
throw e.finalize();
|
1235
|
+
}
|
1236
|
+
|
1237
|
+
UPDATE_TRACE_POINT();
|
1238
|
+
HandshakeSession session(*context, config, SPAWN_THROUGH_PRELOADER);
|
1239
|
+
session.journey.setStepInProgress(SPAWNING_KIT_PREPARATION);
|
1240
|
+
JourneyStep stepToMarkAsErrored = SPAWNING_KIT_PREPARATION;
|
1241
|
+
|
1242
|
+
try {
|
1243
|
+
UPDATE_TRACE_POINT();
|
1244
|
+
HandshakePrepare(session, extraArgs).execute();
|
1245
|
+
createStdChannelFifos(session);
|
1246
|
+
session.journey.setStepPerformed(SPAWNING_KIT_PREPARATION, true);
|
1247
|
+
|
1248
|
+
UPDATE_TRACE_POINT();
|
1249
|
+
ForkResult forkResult = invokeForkCommand(session, stepToMarkAsErrored);
|
1250
|
+
|
1251
|
+
UPDATE_TRACE_POINT();
|
1252
|
+
ScopeGuard guard(boost::bind(nonInterruptableKillAndWaitpid, forkResult.pid));
|
1253
|
+
P_DEBUG("Process forked for appRoot=" << options.appRoot << ": PID " << forkResult.pid);
|
1254
|
+
|
1255
|
+
UPDATE_TRACE_POINT();
|
1256
|
+
session.journey.setStepPerformed(SPAWNING_KIT_PROCESS_RESPONSE_FROM_PRELOADER);
|
1257
|
+
session.journey.setStepInProgress(PRELOADER_PREPARATION);
|
1258
|
+
session.journey.setStepInProgress(SPAWNING_KIT_HANDSHAKE_PERFORM);
|
1259
|
+
stepToMarkAsErrored = SPAWNING_KIT_HANDSHAKE_PERFORM;
|
1260
|
+
HandshakePerform(session, forkResult.pid, forkResult.stdinFd,
|
1261
|
+
forkResult.stdoutAndErrFd, forkResult.alreadyReadStdoutAndErrData).
|
1262
|
+
execute();
|
1263
|
+
guard.clear();
|
1264
|
+
session.journey.setStepPerformed(SPAWNING_KIT_HANDSHAKE_PERFORM);
|
1265
|
+
P_DEBUG("Process spawning done: appRoot=" << options.appRoot <<
|
1266
|
+
", pid=" << forkResult.pid);
|
1267
|
+
return session.result;
|
1268
|
+
} catch (SpawnException &e) {
|
1269
|
+
addPreloaderEnvDumps(e);
|
1270
|
+
throw e;
|
1271
|
+
} catch (const std::exception &originalException) {
|
1272
|
+
session.journey.setStepErrored(stepToMarkAsErrored, true);
|
1273
|
+
SpawnException e(originalException, session.journey,
|
1274
|
+
&config);
|
1275
|
+
addPreloaderEnvDumps(e);
|
1276
|
+
throw e.finalize();
|
1277
|
+
}
|
813
1278
|
}
|
814
1279
|
|
815
1280
|
virtual bool cleanable() const {
|