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.

Files changed (241) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +13 -0
  3. data/CONTRIBUTORS +5 -1
  4. data/build/agent.rb +22 -2
  5. data/build/cxx_tests.rb +41 -5
  6. data/build/misc.rb +4 -1
  7. data/build/support/cxx_dependency_map.rb +1746 -908
  8. data/build/support/vendor/cxx_hinted_parser/CxxHintedParser.sublime-project +8 -0
  9. data/build/support/vendor/cxx_hinted_parser/Gemfile +5 -0
  10. data/build/support/vendor/cxx_hinted_parser/Gemfile.lock +30 -0
  11. data/{src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core → build/support/vendor/cxx_hinted_parser}/LICENSE.md +1 -1
  12. data/build/support/vendor/cxx_hinted_parser/README.md +95 -0
  13. data/build/support/vendor/cxx_hinted_parser/Rakefile +4 -0
  14. 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
  15. data/build/support/vendor/cxx_hinted_parser/lib/cxx_hinted_parser/parser.rb +239 -0
  16. data/dev/ci/README.md +15 -2
  17. data/dev/ci/lib/set-container-envvars.sh +6 -0
  18. data/dev/ci/lib/setup-container.sh +4 -1
  19. data/dev/ci/scripts/debug-console-wrapper.sh +3 -1
  20. data/dev/ci/setup-host +5 -0
  21. data/dev/ci/tests/binaries/Jenkinsfile +105 -0
  22. data/dev/ci/tests/binaries/build-linux +38 -0
  23. data/dev/ci/tests/binaries/build-macos +40 -0
  24. data/dev/ci/tests/binaries/prepare-macos +38 -0
  25. data/dev/ci/tests/binaries/test-linux +45 -0
  26. data/dev/ci/tests/binaries/test-macos +38 -0
  27. data/dev/ci/tests/debian/Jenkinsfile +2 -2
  28. data/dev/ci/tests/rpm/Jenkinsfile +1 -1
  29. data/dev/configkit-schemas/index.json +3 -24
  30. data/dev/vagrant/nginx_rakefile +0 -1
  31. data/package.json +15 -5
  32. data/resources/templates/error_renderer/.editorconfig +19 -0
  33. data/resources/templates/error_renderer/with_details/README.md +9 -0
  34. data/resources/templates/error_renderer/with_details/dist/bundle.js +33 -0
  35. data/resources/templates/error_renderer/with_details/dist/styles.css +17 -0
  36. data/resources/templates/error_renderer/with_details/src/DetailsView.jsx +52 -0
  37. data/resources/templates/error_renderer/with_details/src/GetHelpView.jsx +61 -0
  38. data/resources/templates/error_renderer/with_details/src/JourneyView.css +50 -0
  39. data/resources/templates/error_renderer/with_details/src/JourneyView.jsx +621 -0
  40. data/resources/templates/error_renderer/with_details/src/PageMain.css +114 -0
  41. data/resources/templates/error_renderer/with_details/src/PageMain.jsx +136 -0
  42. data/resources/templates/error_renderer/with_details/src/ProblemDescriptionView.jsx +14 -0
  43. data/resources/templates/error_renderer/with_details/src/ProcessDetailsView.jsx +56 -0
  44. data/resources/templates/error_renderer/with_details/src/SolutionDescriptionView.css +5 -0
  45. data/resources/templates/error_renderer/with_details/src/SolutionDescriptionView.jsx +15 -0
  46. data/resources/templates/error_renderer/with_details/src/SummaryView.jsx +35 -0
  47. data/resources/templates/error_renderer/with_details/src/SystemComponentView.css +34 -0
  48. data/resources/templates/error_renderer/with_details/src/SystemComponentView.jsx +168 -0
  49. data/resources/templates/error_renderer/with_details/src/SystemComponentsView.css +13 -0
  50. data/resources/templates/error_renderer/with_details/src/SystemComponentsView.jsx +116 -0
  51. data/resources/templates/error_renderer/with_details/src/Tab.jsx +12 -0
  52. data/resources/templates/error_renderer/with_details/src/Tabs.jsx +104 -0
  53. data/resources/templates/error_renderer/with_details/src/bootstrap/bootstrap.css +3446 -0
  54. data/resources/templates/error_renderer/with_details/src/bootstrap/bootstrap.js +293 -0
  55. data/resources/templates/error_renderer/with_details/src/bootstrap/config.json +401 -0
  56. data/resources/templates/error_renderer/with_details/src/index.html.template +22 -0
  57. data/resources/templates/error_renderer/with_details/src/index.jsx +23 -0
  58. data/resources/templates/error_renderer/with_details/webpack.config.js +47 -0
  59. data/resources/templates/error_renderer/without_details/dist/bundle.js +1 -0
  60. data/resources/templates/error_renderer/without_details/dist/styles.css +1 -0
  61. data/resources/templates/{undisclosed_error.html.template → error_renderer/without_details/src/index.html.template} +7 -11
  62. data/resources/templates/error_renderer/without_details/src/index.js +1 -0
  63. data/resources/templates/{error_layout.css → error_renderer/without_details/src/main.css} +5 -2
  64. data/resources/templates/error_renderer/without_details/webpack.config.js +42 -0
  65. data/src/agent/AgentMain.cpp +3 -3
  66. data/src/agent/Core/ApplicationPool/BasicProcessInfo.h +13 -0
  67. data/src/agent/Core/ApplicationPool/Common.h +3 -4
  68. data/src/agent/Core/ApplicationPool/Context.h +27 -17
  69. data/src/agent/Core/ApplicationPool/Group.h +3 -1
  70. data/src/agent/Core/ApplicationPool/Group/InitializationAndShutdown.cpp +2 -12
  71. data/src/agent/Core/ApplicationPool/Group/InternalUtils.cpp +55 -10
  72. data/src/agent/Core/ApplicationPool/Group/LifetimeAndBasics.cpp +1 -1
  73. data/src/agent/Core/ApplicationPool/Group/OutOfBandWork.cpp +1 -1
  74. data/src/agent/Core/ApplicationPool/Group/SpawningAndRestarting.cpp +13 -6
  75. data/src/agent/Core/ApplicationPool/Implementation.cpp +16 -100
  76. data/src/agent/Core/ApplicationPool/Options.h +8 -65
  77. data/src/agent/Core/ApplicationPool/Pool.h +4 -21
  78. data/src/agent/Core/ApplicationPool/Pool/AnalyticsCollection.cpp +1 -60
  79. data/src/agent/Core/ApplicationPool/Pool/GeneralUtils.cpp +10 -13
  80. data/src/agent/Core/ApplicationPool/Pool/InitializationAndShutdown.cpp +3 -8
  81. data/src/agent/Core/ApplicationPool/Pool/Miscellaneous.cpp +2 -34
  82. data/src/agent/Core/ApplicationPool/Pool/StateInspection.cpp +1 -1
  83. data/src/agent/Core/ApplicationPool/Process.cpp +17 -12
  84. data/src/agent/Core/ApplicationPool/Process.h +146 -93
  85. data/src/agent/Core/ApplicationPool/Session.h +2 -2
  86. data/src/agent/Core/ApplicationPool/Socket.h +28 -27
  87. data/src/agent/Core/Config.h +1 -3
  88. data/src/agent/Core/ConfigChange.cpp +2 -4
  89. data/src/agent/Core/Controller.h +2 -8
  90. data/src/agent/Core/Controller/BufferBody.cpp +0 -2
  91. data/src/agent/Core/Controller/CheckoutSession.cpp +12 -24
  92. data/src/agent/Core/Controller/Config.h +1 -9
  93. data/src/agent/Core/Controller/ForwardResponse.cpp +0 -34
  94. data/src/agent/Core/Controller/Hooks.cpp +0 -7
  95. data/src/agent/Core/Controller/InitRequest.cpp +0 -43
  96. data/src/agent/Core/Controller/InitializationAndShutdown.cpp +0 -4
  97. data/src/agent/Core/Controller/Request.h +1 -35
  98. data/src/agent/Core/Controller/SendRequest.cpp +0 -32
  99. data/src/agent/Core/CoreMain.cpp +19 -32
  100. data/src/agent/Core/SpawningKit/Config.h +329 -55
  101. data/src/agent/Core/SpawningKit/Config/AutoGeneratedCode.h +369 -0
  102. data/src/agent/Core/SpawningKit/Config/AutoGeneratedCode.h.cxxcodebuilder +307 -0
  103. data/src/agent/Core/SpawningKit/Context.h +211 -0
  104. data/src/agent/Core/SpawningKit/DirectSpawner.h +112 -122
  105. data/src/agent/Core/SpawningKit/DummySpawner.h +59 -20
  106. data/src/agent/Core/SpawningKit/ErrorRenderer.h +117 -0
  107. data/src/agent/Core/SpawningKit/Exceptions.h +1157 -0
  108. data/src/agent/Core/SpawningKit/Factory.h +24 -17
  109. data/src/agent/Core/SpawningKit/{BackgroundIOCapturer.h → Handshake/BackgroundIOCapturer.h} +48 -18
  110. data/src/agent/Core/SpawningKit/Handshake/Perform.h +1650 -0
  111. data/src/agent/Core/SpawningKit/Handshake/Prepare.h +582 -0
  112. data/src/agent/Core/SpawningKit/Handshake/Session.h +91 -0
  113. data/src/agent/Core/SpawningKit/Handshake/WorkDir.h +100 -0
  114. data/src/agent/Core/SpawningKit/Journey.h +561 -0
  115. data/src/agent/Core/SpawningKit/PipeWatcher.h +41 -18
  116. data/src/agent/Core/SpawningKit/README.md +534 -0
  117. data/src/agent/Core/SpawningKit/Result.h +182 -7
  118. data/src/agent/Core/SpawningKit/Result/AutoGeneratedCode.h +69 -0
  119. data/src/agent/Core/SpawningKit/Result/AutoGeneratedCode.h.cxxcodebuilder +110 -0
  120. data/src/agent/Core/SpawningKit/SmartSpawner.h +1027 -562
  121. data/src/agent/Core/SpawningKit/Spawner.h +70 -1134
  122. data/src/agent/Core/SpawningKit/UserSwitchingRules.h +3 -33
  123. data/src/agent/README.md +2 -3
  124. data/src/agent/Shared/ApiServerUtils.h +2 -3
  125. data/src/agent/SpawnEnvSetupper/SpawnEnvSetupperMain.cpp +932 -0
  126. data/src/agent/Watchdog/Config.h +1 -3
  127. data/src/agent/Watchdog/WatchdogMain.cpp +2 -1
  128. data/src/apache2_module/ConfigGeneral/AutoGeneratedDefinitions.cpp +5 -0
  129. data/src/apache2_module/ConfigGeneral/AutoGeneratedManifestDefaultsInitialization.cpp +5 -0
  130. data/src/apache2_module/ConfigGeneral/ManifestGeneration.h +22 -13
  131. data/src/apache2_module/DirConfig/AutoGeneratedCreateFunction.cpp +5 -0
  132. data/src/apache2_module/DirConfig/AutoGeneratedHeaderSerialization.cpp +3 -0
  133. data/src/apache2_module/DirConfig/AutoGeneratedManifestGeneration.cpp +13 -0
  134. data/src/apache2_module/DirConfig/AutoGeneratedMergeFunction.cpp +7 -0
  135. data/src/apache2_module/DirConfig/AutoGeneratedStruct.h +13 -0
  136. data/src/cxx_supportlib/Constants.h +3 -1
  137. data/src/cxx_supportlib/Exceptions.h +0 -121
  138. data/src/cxx_supportlib/LoggingKit/Implementation.cpp +7 -6
  139. data/src/cxx_supportlib/LoggingKit/Logging.h +3 -1
  140. data/src/cxx_supportlib/Utils.cpp +42 -0
  141. data/src/cxx_supportlib/Utils.h +7 -0
  142. data/src/cxx_supportlib/Utils/IOUtils.cpp +58 -0
  143. data/src/cxx_supportlib/Utils/IOUtils.h +13 -0
  144. data/src/cxx_supportlib/Utils/JsonUtils.h +130 -23
  145. data/src/cxx_supportlib/Utils/ScopeGuard.h +9 -4
  146. data/src/cxx_supportlib/Utils/StrIntUtils.cpp +7 -0
  147. data/src/cxx_supportlib/Utils/StrIntUtils.h +1 -0
  148. data/src/cxx_supportlib/Utils/SystemTime.h +1 -1
  149. data/src/cxx_supportlib/Utils/Timer.h +1 -1
  150. data/src/cxx_supportlib/WebSocketCommandReverseServer.h +6 -4
  151. data/src/cxx_supportlib/vendor-copy/adhoc_lve.h +1 -0
  152. data/src/helper-scripts/node-loader.js +54 -59
  153. data/src/helper-scripts/rack-loader.rb +63 -60
  154. data/src/helper-scripts/rack-preloader.rb +125 -72
  155. data/src/helper-scripts/wsgi-loader.py +100 -43
  156. data/src/nginx_module/ConfigGeneral/AutoGeneratedDefinitions.c +120 -112
  157. data/src/nginx_module/ConfigGeneral/AutoGeneratedManifestDefaultsInitialization.c +15 -8
  158. data/src/nginx_module/ConfigGeneral/AutoGeneratedSetterFuncs.c +142 -142
  159. data/src/nginx_module/ConfigGeneral/ManifestGeneration.c +26 -15
  160. data/src/nginx_module/ConfigGeneral/ManifestGeneration.h +3 -0
  161. data/src/nginx_module/LocationConfig/AutoGeneratedCreateFunction.c +76 -70
  162. data/src/nginx_module/LocationConfig/AutoGeneratedHeaderSerialization.c +114 -99
  163. data/src/nginx_module/LocationConfig/AutoGeneratedManifestGeneration.c +170 -156
  164. data/src/nginx_module/LocationConfig/AutoGeneratedMergeFunction.c +38 -35
  165. data/src/nginx_module/LocationConfig/AutoGeneratedStruct.h +5 -1
  166. data/src/ruby_supportlib/phusion_passenger.rb +5 -5
  167. data/src/ruby_supportlib/phusion_passenger/admin_tools/instance.rb +14 -1
  168. data/src/ruby_supportlib/phusion_passenger/apache2/config_options.rb +8 -0
  169. data/src/ruby_supportlib/phusion_passenger/common_library.rb +0 -3
  170. data/src/ruby_supportlib/phusion_passenger/config/nginx_engine_compiler.rb +0 -1
  171. data/src/ruby_supportlib/phusion_passenger/constants.rb +2 -0
  172. data/src/ruby_supportlib/phusion_passenger/loader_shared_helpers.rb +646 -238
  173. data/src/ruby_supportlib/phusion_passenger/nginx/config_options.rb +117 -95
  174. data/src/ruby_supportlib/phusion_passenger/packaging.rb +0 -1
  175. data/src/ruby_supportlib/phusion_passenger/platform_info/depcheck_specs/apache2.rb +5 -1
  176. data/src/ruby_supportlib/phusion_passenger/preloader_shared_helpers.rb +92 -69
  177. data/src/ruby_supportlib/phusion_passenger/public_api.rb +0 -17
  178. data/src/ruby_supportlib/phusion_passenger/rack/thread_handler_extension.rb +0 -3
  179. data/src/ruby_supportlib/phusion_passenger/request_handler.rb +4 -5
  180. data/src/ruby_supportlib/phusion_passenger/request_handler/thread_handler.rb +0 -22
  181. metadata +64 -67
  182. data/resources/templates/error_layout.html.template +0 -86
  183. data/resources/templates/general_error.html.template +0 -1
  184. data/resources/templates/general_error_with_html.html.template +0 -1
  185. data/src/agent/Core/ApplicationPool/ErrorRenderer.h +0 -131
  186. data/src/agent/Core/SpawningKit/Options.h +0 -41
  187. data/src/agent/Core/UnionStation/Connection.h +0 -173
  188. data/src/agent/Core/UnionStation/Context.h +0 -536
  189. data/src/agent/Core/UnionStation/StopwatchLog.h +0 -147
  190. data/src/agent/Core/UnionStation/Transaction.h +0 -249
  191. data/src/agent/SpawnPreparer/SpawnPreparerMain.cpp +0 -208
  192. data/src/cxx_supportlib/UnionStationFilterSupport.cpp +0 -67
  193. data/src/cxx_supportlib/UnionStationFilterSupport.h +0 -1622
  194. data/src/nodejs_supportlib/phusion_passenger/log_express.js +0 -106
  195. data/src/nodejs_supportlib/phusion_passenger/log_mongodb.js +0 -202
  196. data/src/nodejs_supportlib/phusion_passenger/ustreporter.js +0 -227
  197. data/src/nodejs_supportlib/phusion_passenger/ustrouter_connector.js +0 -448
  198. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/CONFIG.md +0 -37
  199. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/Gemfile +0 -17
  200. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/Gemfile.lock +0 -59
  201. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/README-API.md +0 -5
  202. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/README.md +0 -117
  203. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/Rakefile +0 -115
  204. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core.rb +0 -423
  205. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/api.rb +0 -238
  206. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/connection.rb +0 -67
  207. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/context.rb +0 -281
  208. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/lock.rb +0 -62
  209. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/log.rb +0 -66
  210. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/message_channel.rb +0 -157
  211. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/request_reporter.rb +0 -150
  212. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/request_reporter/basics.rb +0 -199
  213. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/request_reporter/controllers.rb +0 -187
  214. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/request_reporter/misc.rb +0 -303
  215. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/request_reporter/view_rendering.rb +0 -91
  216. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/simple_json.rb +0 -396
  217. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/spec_helper.rb +0 -279
  218. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/time_point.rb +0 -39
  219. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/transaction.rb +0 -173
  220. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/utils.rb +0 -177
  221. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/version.rb +0 -32
  222. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/lib/union_station_hooks_core/version_data.rb +0 -44
  223. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/ruby_versions.yml.example +0 -16
  224. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/ruby_versions.yml.travis +0 -20
  225. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/ruby_versions.yml.travis-with-sudo +0 -18
  226. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_core/union_station_hooks_core.gemspec +0 -23
  227. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/Gemfile +0 -14
  228. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/Gemfile.lock +0 -45
  229. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/LICENSE.md +0 -19
  230. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/README.md +0 -104
  231. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/Rakefile +0 -160
  232. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/lib/union_station_hooks_rails.rb +0 -200
  233. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/lib/union_station_hooks_rails/action_controller_extension.rb +0 -45
  234. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/lib/union_station_hooks_rails/action_view_subscriber.rb +0 -55
  235. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/lib/union_station_hooks_rails/active_record_subscriber.rb +0 -41
  236. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/lib/union_station_hooks_rails/active_support_benchmarkable_extension.rb +0 -47
  237. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/lib/union_station_hooks_rails/active_support_cache_subscriber.rb +0 -79
  238. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/lib/union_station_hooks_rails/exception_logger.rb +0 -57
  239. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/lib/union_station_hooks_rails/version.rb +0 -32
  240. data/src/ruby_supportlib/phusion_passenger/vendor/union_station_hooks_rails/lib/union_station_hooks_rails/version_data.rb +0 -44
  241. 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 <jsoncpp/json.h>
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. It is a JSON document
38
- * containing information about the spawned process, such as its PID,
39
- * GUPID, etc. In addition, it contains two file descriptors.
48
+ * Represents the result of a spawning operation.
49
+ *
50
+ * - begin hinted parseable class -
40
51
  */
41
- struct Result: public Json::Value {
42
- FileDescriptor adminSocket;
43
- FileDescriptor errorPipe;
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 <Core/SpawningKit/Spawner.h>
30
- #include <Core/SpawningKit/PipeWatcher.h>
31
- #include <Constants.h>
32
- #include <LoggingKit/LoggingKit.h>
33
- #include <LveLoggingDecorator.h>
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, public boost::enable_shared_from_this<SmartSpawner> {
76
+ class SmartSpawner: public Spawner {
46
77
  private:
47
- /**
48
- * Structure containing arguments and working state for negotiating
49
- * the preloader startup protocol.
50
- */
51
- struct StartupDetails {
52
- /****** Arguments ******/
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 adminSocket;
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
- string getPreloaderCommandString() const {
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
- unsigned int i;
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 (i = 0; i < preloaderCommand.size(); i++) {
92
- if (i != 0) {
93
- result.append(1, '\0');
162
+ for (it = begin; it != end; it++) {
163
+ if (it != begin) {
164
+ result.append(1, ' ');
94
165
  }
95
- result.append(preloaderCommand[i]);
166
+ result.append(escapeShell(*it));
96
167
  }
168
+
97
169
  return result;
98
170
  }
99
171
 
100
- vector<string> createRealPreloaderCommand(const Options &options,
101
- shared_array<const char *> &args)
172
+ void setConfigFromAppPoolOptions(Config *config, Json::Value &extraArgs,
173
+ const AppPoolOptions &options)
102
174
  {
103
- string agentFilename = config->resourceLocator->findSupportBinary(AGENT_EXE);
104
- vector<string> command;
105
-
106
- if (shouldLoadShellEnvvars(options, preparation)) {
107
- command.push_back(preparation.userSwitching.shell);
108
- command.push_back(preparation.userSwitching.shell);
109
- if (LoggingKit::getLevel() >= LoggingKit::DEBUG3) {
110
- command.push_back("-lxc");
111
- } else {
112
- command.push_back("-lc");
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
- createCommandArgs(command, args);
132
- return command;
133
- }
209
+ typedef boost::shared_ptr<StdChannelsAsyncOpenState> StdChannelsAsyncOpenStatePtr;
134
210
 
135
- void throwPreloaderSpawnException(const string &msg,
136
- SpawnException::ErrorKind errorKind,
137
- StartupDetails &details)
211
+ StdChannelsAsyncOpenStatePtr openStdChannelsFifosAsynchronously(
212
+ HandshakeSession &session)
138
213
  {
139
- throwPreloaderSpawnException(msg, errorKind, details.stderrCapturer,
140
- *details.options, details.debugDir);
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 throwPreloaderSpawnException(const string &msg,
144
- SpawnException::ErrorKind errorKind,
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
- // Stop the stderr capturing thread and get the captured stderr
151
- // output so far.
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
- try {
170
- ret = readExact(stderrCapturer->getFd(), buf,
171
- sizeof(buf), &timeout);
172
- if (ret == 0) {
173
- done = true;
174
- } else {
175
- stderrOutput.append(buf, ret);
176
- }
177
- } catch (const SystemException &e) {
178
- P_WARN("Stderr I/O capture error: " << e.what());
179
- done = true;
180
- } catch (const TimeoutException &) {
181
- done = true;
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
- stderrCapturer.reset();
280
+ }
186
281
 
187
- // Now throw SpawnException with the captured stderr output
188
- // as error response.
189
- SpawnException e(msg,
190
- createErrorPageFromStderrOutput(msg, errorKind, stderrOutput),
191
- true,
192
- errorKind);
193
- e.setPreloaderCommand(getPreloaderCommandString());
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 annotatePreloaderException(SpawnException &e, const DebugDirPtr &debugDir) {
199
- if (debugDir != NULL) {
200
- e.addAnnotations(debugDir->readAll());
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
- shared_array<const char *> args;
217
- preparation = prepareSpawn(options);
218
- vector<string> command = createRealPreloaderCommand(options, args);
219
- SocketPair adminSocket = createUnixSocketPair(__FILE__, __LINE__);
220
- Pipe errorPipe = createPipe(__FILE__, __LINE__);
221
- DebugDirPtr debugDir = boost::make_shared<DebugDir>(preparation.userSwitching.uid,
222
- preparation.userSwitching.gid);
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
- preparation.userSwitching.uid,
226
- options.lveMinUid,
227
- LveLoggingDecorator::lveExitCallback);
345
+ session.uid,
346
+ config.lveMinUid,
347
+ LveLoggingDecorator::lveExitCallback);
228
348
  LveLoggingDecorator::logLveEnter(scopedLveEnter,
229
- preparation.userSwitching.uid,
230
- options.lveMinUid);
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 adminSocketCopy = dup2(adminSocket.first, 3);
239
- int errorPipeCopy = dup2(errorPipe.second, 4);
240
- dup2(adminSocketCopy, 0);
241
- dup2(adminSocketCopy, 1);
242
- dup2(errorPipeCopy, 2);
243
- closeAllFileDescriptors(2);
244
- setChroot(preparation);
245
- setUlimits(options);
246
- switchUser(preparation);
247
- setWorkingDirectory(preparation);
248
- execvp(command[0].c_str(), (char * const *) args.get());
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
- command[0].c_str(), strerror(e), e);
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
- throw SystemException("Cannot fork a new process", e);
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
- UPDATE_TRACE_POINT();
269
- P_LOG_FILE_DESCRIPTOR_PURPOSE(adminSocket.first,
270
- "Preloader " << pid << " (" << options.appRoot << ") adminSocket[0]");
271
- P_LOG_FILE_DESCRIPTOR_PURPOSE(adminSocket.second,
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 << ": PID " << pid);
281
- adminSocket.first.close();
282
- errorPipe.second.close();
283
-
284
- StartupDetails details;
285
- details.pid = pid;
286
- details.adminSocket = adminSocket.second;
287
- details.io = BufferedIO(adminSocket.second);
288
- details.stderrCapturer =
289
- boost::make_shared<BackgroundIOCapturer>(
290
- errorPipe.first,
291
- pid,
292
- // The cast works around a compilation problem in Clang.
293
- (const char *) "stderr",
294
- options.getAppGroupName(),
295
- options.appLogFile);
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
- watcher = boost::make_shared<PipeWatcher>(config,
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
- preloaderAnnotations = debugDir->readAll();
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
- syscalls::shutdown(adminSocket, SHUT_WR);
460
+
461
+ preloaderStdin.close(false);
462
+
341
463
  if (timedWaitpid(pid, NULL, 5000) == 0) {
342
- P_TRACE(2, "Spawn server did not exit in time, killing it...");
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
- void sendStartupRequest(StartupDetails &details) {
487
+ FileDescriptor connectToPreloader(HandshakeSession &session) {
361
488
  TRACE_POINT();
362
- try {
363
- const size_t UNIX_PATH_MAX = sizeof(((struct sockaddr_un *) 0)->sun_path);
364
- string data = "You have control 1.0\n"
365
- "passenger_root: " + config->resourceLocator->getInstallSpec() + "\n"
366
- "ruby_libdir: " + config->resourceLocator->getRubyLibDir() + "\n"
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
- vector<string> args;
378
- vector<string>::const_iterator it, end;
379
- details.options->toVector(args, *config->resourceLocator, Options::SPAWN_OPTIONS);
380
- for (it = args.begin(); it != args.end(); it++) {
381
- const string &key = *it;
382
- it++;
383
- const string &value = *it;
384
- data.append(key + ": " + value + "\n");
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
- vector<StaticString> lines;
388
- split(data, '\n', lines);
389
- foreach (const StaticString line, lines) {
390
- P_DEBUG("[App " << details.pid << " stdin >>] " << line);
391
- }
392
- writeExact(details.adminSocket, data, &details.timeout);
393
- writeExact(details.adminSocket, "\n", &details.timeout);
394
- } catch (const SystemException &e) {
395
- if (e.code() == EPIPE) {
396
- /* Ignore this. Process might have written an
397
- * error response before reading the arguments,
398
- * in which case we'll want to show that instead.
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
- throwPreloaderSpawnException("An error occurred while starting up "
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
- string handleStartupResponse(StartupDetails &details) {
543
+ ForkResult invokeForkCommand(HandshakeSession &session, JourneyStep &stepToMarkAsErrored) {
417
544
  TRACE_POINT();
418
- string socketAddress;
419
545
 
420
- while (true) {
421
- string line;
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
- line = readMessageLine(details);
425
- } catch (const SystemException &e) {
426
- throwPreloaderSpawnException("An error occurred while starting up "
427
- "the preloader. There was an I/O error while reading its "
428
- "startup response: " + e.sys(),
429
- SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
430
- details);
431
- } catch (const TimeoutException &) {
432
- throwPreloaderSpawnException("An error occurred while starting up "
433
- "the preloader: it did not write a startup response in time. "
434
- "If your app needs more time to start you can increase the "
435
- "Passenger start timeout config option.",
436
- SpawnException::PRELOADER_STARTUP_TIMEOUT,
437
- details);
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
- if (line.empty()) {
441
- throwPreloaderSpawnException("An error occurred while starting up "
442
- "the preloader. It unexpected closed the connection while "
443
- "sending its startup response.",
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
- string::size_type pos = line.find(": ");
457
- if (pos == string::npos) {
458
- throwPreloaderSpawnException("An error occurred while starting up "
459
- "the preloader. It sent a startup response line without "
460
- "separator.",
461
- SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
462
- details);
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
- string key = line.substr(0, pos);
466
- string value = line.substr(pos + 2, line.size() - pos - 3);
467
- if (key == "socket") {
468
- // TODO: validate socket address here
469
- socketAddress = fixupSocketAddress(options, value);
470
- } else {
471
- throwPreloaderSpawnException("An error occurred while starting up "
472
- "the preloader. It sent an unknown startup response line "
473
- "called '" + key + "'.",
474
- SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
475
- details);
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
- if (socketAddress.empty()) {
480
- throwPreloaderSpawnException("An error occurred while starting up "
481
- "the preloader. It did not report a socket address in its "
482
- "startup response.",
483
- SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
484
- details);
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
- return socketAddress;
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 handleErrorResponse(StartupDetails &details) {
693
+ void sendForkCommand(HandshakeSession &session, const FileDescriptor &fd) {
491
694
  TRACE_POINT();
492
- map<string, string> attributes;
695
+ Json::Value doc;
493
696
 
494
- while (true) {
495
- string line;
697
+ doc["command"] = "spawn";
698
+ doc["work_dir"] = session.workDir->getPath();
496
699
 
497
- try {
498
- line = readMessageLine(details);
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
- if (line.empty()) {
515
- throwPreloaderSpawnException("An error occurred while starting up "
516
- "the preloader. It unexpected closed the connection while "
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
- string::size_type pos = line.find(": ");
531
- if (pos == string::npos) {
532
- throwPreloaderSpawnException("An error occurred while starting up "
533
- "the preloader. It sent a startup response line without "
534
- "separator.",
535
- SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
536
- details);
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
- string key = line.substr(0, pos);
540
- string value = line.substr(pos + 2, line.size() - pos - 3);
541
- attributes[key] = value;
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
- try {
545
- string message = details.io.readAll(&details.timeout);
546
- SpawnException e("An error occurred while starting up the preloader.",
547
- message,
548
- attributes["html"] == "true",
549
- SpawnException::PRELOADER_STARTUP_EXPLAINABLE_ERROR);
550
- e.setPreloaderCommand(getPreloaderCommandString());
551
- annotatePreloaderException(e, details.debugDir);
552
- throwSpawnException(e, *details.options);
553
- } catch (const SystemException &e) {
554
- throwPreloaderSpawnException("An error occurred while starting up "
555
- "the preloader. It tried to report an error message, but "
556
- "an I/O error occurred while reading this error message: " +
557
- e.sys(),
558
- SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
559
- details);
560
- } catch (const TimeoutException &) {
561
- throwPreloaderSpawnException("An error occurred while starting up "
562
- "the preloader. It tried to report an error message, but "
563
- "it took too much time doing that.",
564
- SpawnException::PRELOADER_STARTUP_TIMEOUT,
565
- details);
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
- string negotiatePreloaderStartup(StartupDetails &details) {
586
- TRACE_POINT();
587
- string result;
588
- try {
589
- result = readMessageLine(details);
590
- } catch (const SystemException &e) {
591
- throwPreloaderSpawnException("An error occurred while starting up "
592
- "the preloader. There was an I/O error while reading its "
593
- "handshake message: " + e.sys(),
594
- SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR,
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
- if (result == "Ready\n") {
623
- return handleStartupResponse(details);
624
- } else if (result == "Error\n") {
625
- handleErrorResponse(details);
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
- if (result == "Error\n") {
632
- handleErrorResponse(details);
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
- handleInvalidResponseType(details, result);
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
- // Never reached, shut up compiler warning.
639
- abort();
640
- return "";
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
- NegotiationDetails sendSpawnCommandAndGetNegotiationDetails(const Options &options) {
644
- TRACE_POINT();
645
- NegotiationDetails details;
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
- details.preparation = &preparation;
648
- details.options = &options;
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
- try {
651
- sendSpawnCommand(details);
652
- } catch (const SystemException &e) {
653
- sendSpawnCommandAgain(e, details);
654
- } catch (const IOException &e) {
655
- sendSpawnCommandAgain(e, details);
656
- } catch (const SpawnException &e) {
657
- sendSpawnCommandAgain(e, details);
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
- return details;
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
- void sendSpawnCommand(NegotiationDetails &details) {
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
- const Options &options = *details.options;
666
- FileDescriptor fd;
962
+ uid_t uid = (uid_t) -1;
667
963
 
668
964
  try {
669
- fd.assign(connectToServer(socketAddress, __FILE__, __LINE__), NULL, 0);
670
- } catch (const SystemException &e) {
671
- BackgroundIOCapturerPtr stderrCapturer;
672
- throwPreloaderSpawnException("An error occurred while starting "
673
- "the application. Unable to connect to the preloader's "
674
- "socket: " + string(e.what()),
675
- SpawnException::APP_STARTUP_PROTOCOL_ERROR,
676
- stderrCapturer,
677
- options,
678
- DebugDirPtr());
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
- BufferedIO io(fd);
685
- unsigned long long timeout = options.startTimeout * 1000;
686
- string result;
687
- vector<string> args;
688
- vector<string>::const_iterator it;
689
-
690
- writeExact(fd, "spawn\n", &timeout);
691
- options.toVector(args, *config->resourceLocator, Options::SPAWN_OPTIONS);
692
- for (it = args.begin(); it != args.end(); it++) {
693
- const string &key = *it;
694
- it++;
695
- const string &value = *it;
696
- writeExact(fd, key + ": " + value + "\n", &timeout);
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
- writeExact(fd, "\n", &timeout);
1067
+ }
699
1068
 
700
- result = io.readLine(1024 * 8, &timeout);
701
- if (result == "OK\n") {
702
- UPDATE_TRACE_POINT();
703
- pid_t spawnedPid;
704
-
705
- spawnedPid = atoi(io.readLine(1024 * 8, &timeout).c_str());
706
- if (spawnedPid <= 0) {
707
- BackgroundIOCapturerPtr stderrCapturer;
708
- throwPreloaderSpawnException("An error occurred while starting "
709
- "the web application. Its preloader responded to the "
710
- "'spawn' command with an invalid PID: '" +
711
- toString(spawnedPid) + "'",
712
- SpawnException::APP_STARTUP_PROTOCOL_ERROR,
713
- stderrCapturer,
714
- options,
715
- DebugDirPtr());
716
- }
717
- // TODO: we really should be checking UID.
718
- // FIXME: for Passenger 4 we *must* check the UID otherwise this is a gaping security hole.
719
- if (getsid(spawnedPid) != getsid(pid)) {
720
- BackgroundIOCapturerPtr stderrCapturer;
721
- throwPreloaderSpawnException("An error occurred while starting "
722
- "the web application. Its preloader responded to the "
723
- "'spawn' command with a PID that doesn't belong to "
724
- "the same session: '" + toString(spawnedPid) + "'",
725
- SpawnException::APP_STARTUP_PROTOCOL_ERROR,
726
- stderrCapturer,
727
- options,
728
- DebugDirPtr());
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
- P_LOG_FILE_DESCRIPTOR_PURPOSE(fd, "App " << spawnedPid
732
- << " (" << options.appRoot << ") adminSocket[1]");
733
- details.pid = spawnedPid;
734
- details.adminSocket = fd;
735
- details.io = io;
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
- } else if (result == "Error\n") {
738
- UPDATE_TRACE_POINT();
739
- details.io = io;
740
- details.timeout = timeout;
741
- handleSpawnErrorResponse(details);
1123
+ static void doClosedir(DIR *dir) {
1124
+ closedir(dir);
1125
+ }
742
1126
 
743
- } else {
744
- UPDATE_TRACE_POINT();
745
- handleInvalidSpawnResponseType(result, details);
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
- template<typename Exception>
750
- void sendSpawnCommandAgain(const Exception &e, NegotiationDetails &details) {
751
- TRACE_POINT();
752
- P_WARN("An error occurred while spawning a process: " << e.what());
753
- P_WARN("The application preloader seems to have crashed, restarting it and trying again...");
754
- stopPreloader();
755
- startPreloader();
756
- ScopeGuard guard(boost::bind(&SmartSpawner::stopPreloader, this));
757
- sendSpawnCommand(details);
758
- guard.clear();
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
- protected:
762
- virtual void annotateAppSpawnException(SpawnException &e, NegotiationDetails &details) {
763
- Spawner::annotateAppSpawnException(e, details);
764
- e.addAnnotations(preloaderAnnotations);
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(const vector<string> &_preloaderCommand,
769
- const Options &_options,
770
- const ConfigPtr &_config)
771
- : Spawner(_config),
772
- preloaderCommand(_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().detachFromUnionStationTransaction();
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 Options &options) {
1205
+ virtual Result spawn(const AppPoolOptions &options) {
789
1206
  TRACE_POINT();
790
- assert(options.appType == this->options.appType);
791
- assert(options.appRoot == this->options.appRoot);
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
- NegotiationDetails details = sendSpawnCommandAndGetNegotiationDetails(options);
809
- Result result = negotiateSpawn(details);
810
- P_DEBUG("Process spawning done: appRoot=" << options.appRoot <<
811
- ", pid=" << result["pid"].asInt());
812
- return result;
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 {