passenger 5.0.0.beta3 → 5.0.0.rc1

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 (218) hide show
  1. checksums.yaml +8 -8
  2. checksums.yaml.gz.asc +7 -7
  3. data.tar.gz.asc +7 -7
  4. data/.editorconfig +11 -5
  5. data/CHANGELOG +38 -0
  6. data/CONTRIBUTING.md +1 -4
  7. data/Gemfile +0 -1
  8. data/Gemfile.lock +0 -2
  9. data/Rakefile +33 -33
  10. data/bin/passenger +1 -1
  11. data/bin/passenger-config +1 -1
  12. data/bin/passenger-install-apache2-module +800 -800
  13. data/bin/passenger-install-nginx-module +592 -592
  14. data/bin/passenger-memory-stats +127 -127
  15. data/bin/passenger-status +216 -216
  16. data/build/agents.rb +127 -127
  17. data/build/apache2.rb +87 -87
  18. data/build/basics.rb +60 -60
  19. data/build/common_library.rb +165 -165
  20. data/build/cplusplus_support.rb +51 -51
  21. data/build/cxx_tests.rb +268 -268
  22. data/build/debian.rb +143 -143
  23. data/build/documentation.rb +58 -58
  24. data/build/integration_tests.rb +81 -81
  25. data/build/misc.rb +132 -132
  26. data/build/nginx.rb +20 -20
  27. data/build/node_tests.rb +7 -7
  28. data/build/oxt_tests.rb +14 -14
  29. data/build/packaging.rb +570 -570
  30. data/build/preprocessor.rb +260 -260
  31. data/build/rake_extensions.rb +71 -71
  32. data/build/ruby_extension.rb +29 -29
  33. data/build/ruby_tests.rb +6 -6
  34. data/build/test_basics.rb +37 -37
  35. data/debian.template/control.template +3 -5
  36. data/dev/copy_boost_headers +134 -134
  37. data/dev/install_scripts_bootstrap_code.rb +25 -25
  38. data/dev/list_tests +20 -20
  39. data/dev/ruby_server.rb +223 -223
  40. data/dev/runner +18 -18
  41. data/doc/ServerOptimizationGuide.txt.md +55 -2
  42. data/doc/Users guide Nginx.txt +0 -26
  43. data/doc/Users guide Standalone.txt +5 -1
  44. data/doc/users_guide_snippets/tips.txt +9 -0
  45. data/ext/common/ApplicationPool2/Group.h +23 -11
  46. data/ext/common/ApplicationPool2/Implementation.cpp +32 -7
  47. data/ext/common/ApplicationPool2/Pool.h +22 -17
  48. data/ext/common/ApplicationPool2/SmartSpawner.h +4 -1
  49. data/ext/common/ApplicationPool2/Spawner.h +1 -1
  50. data/ext/common/Constants.h +1 -1
  51. data/ext/common/agents/Base.cpp +35 -20
  52. data/ext/common/agents/HelperAgent/Main.cpp +8 -1
  53. data/ext/common/agents/HelperAgent/OptionParser.h +18 -4
  54. data/ext/common/agents/HelperAgent/RequestHandler.h +2 -83
  55. data/ext/common/agents/HelperAgent/RequestHandler/ForwardResponse.cpp +54 -1
  56. data/ext/common/agents/HelperAgent/RequestHandler/InitRequest.cpp +7 -4
  57. data/ext/common/agents/Main.cpp +1 -1
  58. data/ext/common/agents/Watchdog/Main.cpp +54 -19
  59. data/ext/nginx/Configuration.c +7 -0
  60. data/ext/nginx/ContentHandler.c +9 -1
  61. data/helper-scripts/backtrace-sanitizer.rb +106 -87
  62. data/helper-scripts/crash-watch.rb +32 -0
  63. data/helper-scripts/download_binaries/extconf.rb +38 -38
  64. data/helper-scripts/meteor-loader.rb +107 -107
  65. data/helper-scripts/prespawn +101 -101
  66. data/helper-scripts/rack-loader.rb +96 -96
  67. data/helper-scripts/rack-preloader.rb +137 -137
  68. data/lib/phusion_passenger.rb +292 -292
  69. data/lib/phusion_passenger/abstract_installer.rb +438 -438
  70. data/lib/phusion_passenger/active_support3_extensions/init.rb +168 -170
  71. data/lib/phusion_passenger/admin_tools.rb +20 -20
  72. data/lib/phusion_passenger/admin_tools/instance.rb +178 -178
  73. data/lib/phusion_passenger/admin_tools/instance_registry.rb +61 -61
  74. data/lib/phusion_passenger/admin_tools/memory_stats.rb +267 -267
  75. data/lib/phusion_passenger/apache2/config_options.rb +182 -182
  76. data/lib/phusion_passenger/common_library.rb +479 -485
  77. data/lib/phusion_passenger/config/about_command.rb +161 -161
  78. data/lib/phusion_passenger/config/admin_command_command.rb +129 -129
  79. data/lib/phusion_passenger/config/agent_compiler.rb +121 -121
  80. data/lib/phusion_passenger/config/build_native_support_command.rb +43 -43
  81. data/lib/phusion_passenger/config/command.rb +25 -25
  82. data/lib/phusion_passenger/config/compile_agent_command.rb +62 -62
  83. data/lib/phusion_passenger/config/compile_nginx_engine_command.rb +88 -73
  84. data/lib/phusion_passenger/config/detach_process_command.rb +72 -72
  85. data/lib/phusion_passenger/config/download_agent_command.rb +246 -227
  86. data/lib/phusion_passenger/config/download_nginx_engine_command.rb +245 -224
  87. data/lib/phusion_passenger/config/install_agent_command.rb +144 -132
  88. data/lib/phusion_passenger/config/install_standalone_runtime_command.rb +205 -185
  89. data/lib/phusion_passenger/config/installation_utils.rb +204 -204
  90. data/lib/phusion_passenger/config/list_instances_command.rb +64 -64
  91. data/lib/phusion_passenger/config/main.rb +152 -152
  92. data/lib/phusion_passenger/config/nginx_engine_compiler.rb +319 -300
  93. data/lib/phusion_passenger/config/reopen_logs_command.rb +67 -67
  94. data/lib/phusion_passenger/config/restart_app_command.rb +155 -155
  95. data/lib/phusion_passenger/config/system_metrics_command.rb +13 -13
  96. data/lib/phusion_passenger/config/utils.rb +95 -95
  97. data/lib/phusion_passenger/config/validate_install_command.rb +198 -198
  98. data/lib/phusion_passenger/console_text_template.rb +25 -25
  99. data/lib/phusion_passenger/constants.rb +90 -90
  100. data/lib/phusion_passenger/debug_logging.rb +106 -106
  101. data/lib/phusion_passenger/loader_shared_helpers.rb +447 -432
  102. data/lib/phusion_passenger/message_channel.rb +312 -312
  103. data/lib/phusion_passenger/message_client.rb +176 -176
  104. data/lib/phusion_passenger/native_support.rb +369 -369
  105. data/lib/phusion_passenger/nginx/config_options.rb +297 -297
  106. data/lib/phusion_passenger/packaging.rb +131 -131
  107. data/lib/phusion_passenger/platform_info.rb +360 -360
  108. data/lib/phusion_passenger/platform_info/apache.rb +767 -767
  109. data/lib/phusion_passenger/platform_info/apache_detector.rb +199 -199
  110. data/lib/phusion_passenger/platform_info/binary_compatibility.rb +107 -107
  111. data/lib/phusion_passenger/platform_info/compiler.rb +570 -570
  112. data/lib/phusion_passenger/platform_info/curl.rb +32 -32
  113. data/lib/phusion_passenger/platform_info/cxx_portability.rb +188 -188
  114. data/lib/phusion_passenger/platform_info/depcheck.rb +372 -372
  115. data/lib/phusion_passenger/platform_info/depcheck_specs/apache2.rb +109 -109
  116. data/lib/phusion_passenger/platform_info/depcheck_specs/compiler_toolchain.rb +4 -4
  117. data/lib/phusion_passenger/platform_info/depcheck_specs/gems.rb +10 -34
  118. data/lib/phusion_passenger/platform_info/depcheck_specs/libs.rb +101 -101
  119. data/lib/phusion_passenger/platform_info/depcheck_specs/ruby.rb +5 -5
  120. data/lib/phusion_passenger/platform_info/depcheck_specs/utilities.rb +13 -13
  121. data/lib/phusion_passenger/platform_info/linux.rb +55 -55
  122. data/lib/phusion_passenger/platform_info/operating_system.rb +149 -149
  123. data/lib/phusion_passenger/platform_info/ruby.rb +468 -448
  124. data/lib/phusion_passenger/platform_info/zlib.rb +9 -9
  125. data/lib/phusion_passenger/plugin.rb +66 -66
  126. data/lib/phusion_passenger/preloader_shared_helpers.rb +126 -126
  127. data/lib/phusion_passenger/public_api.rb +191 -191
  128. data/lib/phusion_passenger/rack/out_of_band_gc.rb +93 -94
  129. data/lib/phusion_passenger/rack/thread_handler_extension.rb +231 -227
  130. data/lib/phusion_passenger/request_handler.rb +567 -577
  131. data/lib/phusion_passenger/request_handler/thread_handler.rb +379 -381
  132. data/lib/phusion_passenger/ruby_core_enhancements.rb +86 -86
  133. data/lib/phusion_passenger/ruby_core_io_enhancements.rb +74 -74
  134. data/lib/phusion_passenger/simple_benchmarking.rb +25 -25
  135. data/lib/phusion_passenger/standalone/app_finder.rb +153 -150
  136. data/lib/phusion_passenger/standalone/command.rb +44 -40
  137. data/lib/phusion_passenger/standalone/config_utils.rb +53 -53
  138. data/lib/phusion_passenger/standalone/control_utils.rb +38 -59
  139. data/lib/phusion_passenger/standalone/main.rb +73 -73
  140. data/lib/phusion_passenger/standalone/start_command.rb +697 -685
  141. data/lib/phusion_passenger/standalone/start_command/builtin_engine.rb +193 -155
  142. data/lib/phusion_passenger/standalone/start_command/nginx_engine.rb +162 -133
  143. data/lib/phusion_passenger/standalone/status_command.rb +64 -64
  144. data/lib/phusion_passenger/standalone/stop_command.rb +72 -72
  145. data/lib/phusion_passenger/standalone/version_command.rb +9 -9
  146. data/lib/phusion_passenger/union_station/connection.rb +32 -32
  147. data/lib/phusion_passenger/union_station/core.rb +251 -251
  148. data/lib/phusion_passenger/union_station/transaction.rb +126 -126
  149. data/lib/phusion_passenger/utils.rb +199 -167
  150. data/lib/phusion_passenger/utils/ansi_colors.rb +128 -128
  151. data/lib/phusion_passenger/utils/download.rb +196 -196
  152. data/lib/phusion_passenger/utils/file_system_watcher.rb +158 -158
  153. data/lib/phusion_passenger/utils/hosts_file_parser.rb +101 -101
  154. data/lib/phusion_passenger/utils/lock.rb +31 -31
  155. data/lib/phusion_passenger/utils/native_support_utils.rb +31 -31
  156. data/lib/phusion_passenger/utils/progress_bar.rb +26 -26
  157. data/lib/phusion_passenger/utils/shellwords.rb +20 -20
  158. data/lib/phusion_passenger/utils/terminal_choice_menu.rb +206 -206
  159. data/lib/phusion_passenger/utils/unseekable_socket.rb +272 -272
  160. data/lib/phusion_passenger/vendor/crash_watch/app.rb +129 -0
  161. data/lib/phusion_passenger/vendor/crash_watch/gdb_controller.rb +341 -0
  162. data/lib/phusion_passenger/vendor/crash_watch/version.rb +24 -0
  163. data/lib/phusion_passenger/vendor/daemon_controller.rb +877 -0
  164. data/lib/phusion_passenger/vendor/daemon_controller/lock_file.rb +127 -0
  165. data/lib/phusion_passenger/vendor/daemon_controller/spawn.rb +26 -0
  166. data/lib/phusion_passenger/vendor/daemon_controller/version.rb +29 -0
  167. data/packaging/rpm/passenger_spec/passenger.spec.template +0 -1
  168. data/passenger.gemspec +0 -1
  169. data/resources/templates/config/nginx_engine_compiler/possible_solutions_for_download_and_extraction_problems.txt.erb +27 -0
  170. data/resources/templates/standalone/config.erb +19 -15
  171. data/test/integration_tests/apache2_tests.rb +566 -566
  172. data/test/integration_tests/downloaded_binaries_tests.rb +126 -125
  173. data/test/integration_tests/native_packaging_spec.rb +296 -296
  174. data/test/integration_tests/nginx_tests.rb +393 -393
  175. data/test/integration_tests/shared/example_webapp_tests.rb +282 -280
  176. data/test/integration_tests/source_packaging_test.rb +138 -138
  177. data/test/integration_tests/spec_helper.rb +5 -5
  178. data/test/integration_tests/standalone_tests.rb +367 -367
  179. data/test/ruby/debug_logging_spec.rb +133 -133
  180. data/test/ruby/message_channel_spec.rb +186 -186
  181. data/test/ruby/rack/loader_spec.rb +28 -28
  182. data/test/ruby/rack/preloader_spec.rb +34 -34
  183. data/test/ruby/rails3.0/loader_spec.rb +12 -12
  184. data/test/ruby/rails3.0/preloader_spec.rb +18 -18
  185. data/test/ruby/rails3.1/loader_spec.rb +12 -12
  186. data/test/ruby/rails3.1/preloader_spec.rb +18 -18
  187. data/test/ruby/rails3.2/loader_spec.rb +12 -12
  188. data/test/ruby/rails3.2/preloader_spec.rb +18 -18
  189. data/test/ruby/rails4.0/loader_spec.rb +12 -12
  190. data/test/ruby/rails4.0/preloader_spec.rb +18 -18
  191. data/test/ruby/rails4.1/loader_spec.rb +12 -12
  192. data/test/ruby/rails4.1/preloader_spec.rb +18 -18
  193. data/test/ruby/request_handler_spec.rb +730 -730
  194. data/test/ruby/shared/loader_sharedspec.rb +224 -224
  195. data/test/ruby/shared/rails/union_station_extensions_sharedspec.rb +327 -327
  196. data/test/ruby/shared/ruby_loader_sharedspec.rb +47 -47
  197. data/test/ruby/spec_helper.rb +65 -65
  198. data/test/ruby/standalone/runtime_installer_spec.rb +384 -384
  199. data/test/ruby/union_station_spec.rb +276 -276
  200. data/test/ruby/utils/file_system_watcher_spec.rb +220 -220
  201. data/test/ruby/utils/hosts_file_parser.rb +248 -248
  202. data/test/ruby/utils/tee_input_spec.rb +215 -215
  203. data/test/ruby/utils/unseekable_socket_spec.rb +57 -57
  204. data/test/ruby/utils_spec.rb +21 -21
  205. data/test/stub/rack/config.ru +87 -87
  206. data/test/stub/rack/library.rb +8 -8
  207. data/test/stub/rack/start.rb +30 -30
  208. data/test/support/apache2_controller.rb +191 -191
  209. data/test/support/nginx_controller.rb +90 -99
  210. data/test/support/placebo-preloader.rb +57 -57
  211. data/test/support/test_helper.rb +435 -435
  212. metadata +11 -21
  213. metadata.gz.asc +7 -7
  214. data/lib/phusion_passenger/standalone/command2.rb +0 -292
  215. data/lib/phusion_passenger/standalone/start2_command.rb +0 -799
  216. data/resources/templates/standalone/download_tool_missing.txt.erb +0 -18
  217. data/resources/templates/standalone/possible_solutions_for_download_and_extraction_problems.txt.erb +0 -17
  218. data/resources/templates/standalone/run_installer_as_root.txt.erb +0 -8
@@ -0,0 +1,24 @@
1
+ # Copyright (c) 2010-2015 Phusion
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ module CrashWatch
23
+ VERSION_STRING = '1.1.12'
24
+ end
@@ -0,0 +1,877 @@
1
+ # daemon_controller, library for robust daemon management
2
+ # Copyright (c) 2010-2015 Phusion
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+
22
+ require 'tempfile'
23
+ require 'fcntl'
24
+ require 'socket'
25
+ require 'timeout'
26
+ if Process.respond_to?(:spawn)
27
+ require 'rbconfig'
28
+ end
29
+
30
+ PhusionPassenger.require_passenger_lib 'vendor/daemon_controller/lock_file'
31
+
32
+ module PhusionPassenger
33
+
34
+ # Main daemon controller object. See the README for an introduction and tutorial.
35
+ class DaemonController
36
+ ALLOWED_CONNECT_EXCEPTIONS = [Errno::ECONNREFUSED, Errno::ENETUNREACH,
37
+ Errno::ETIMEDOUT, Errno::ECONNRESET, Errno::EINVAL,
38
+ Errno::EADDRNOTAVAIL]
39
+
40
+ SPAWNER_FILE = File.expand_path(File.join(File.dirname(__FILE__),
41
+ "daemon_controller", "spawn.rb"))
42
+
43
+ class Error < StandardError
44
+ end
45
+ class TimeoutError < Error
46
+ end
47
+ class AlreadyStarted < Error
48
+ end
49
+ class StartError < Error
50
+ end
51
+ class StartTimeout < TimeoutError
52
+ end
53
+ class StopError < Error
54
+ end
55
+ class StopTimeout < TimeoutError
56
+ end
57
+ class ConnectError < Error
58
+ end
59
+ class DaemonizationTimeout < TimeoutError
60
+ end
61
+
62
+ # Create a new DaemonController object.
63
+ #
64
+ # === Mandatory options
65
+ #
66
+ # [:identifier]
67
+ # A human-readable, unique name for this daemon, e.g. "Sphinx search server".
68
+ # This identifier will be used in some error messages. On some platforms, it will
69
+ # be used for concurrency control: on such platforms, no two DaemonController
70
+ # objects will operate on the same identifier on the same time.
71
+ #
72
+ # [:start_command]
73
+ # The command to start the daemon. This must be a a String, e.g.
74
+ # "mongrel_rails start -e production", or a Proc which returns a String.
75
+ #
76
+ # If the value is a Proc, and the +before_start+ option is given too, then
77
+ # the +start_command+ Proc is guaranteed to be called after the +before_start+
78
+ # Proc is called.
79
+ #
80
+ # [:ping_command]
81
+ # The ping command is used to check whether the daemon can be connected to.
82
+ # It is also used to ensure that #start only returns when the daemon can be
83
+ # connected to.
84
+ #
85
+ # The value may be a command string. This command must exit with an exit code of
86
+ # 0 if the daemon can be successfully connected to, or exit with a non-0 exit
87
+ # code on failure.
88
+ #
89
+ # The value may also be an Array which specifies the socket address of the daemon.
90
+ # It must be in one of the following forms:
91
+ # - [:tcp, host_name, port]
92
+ # - [:unix, filename]
93
+ #
94
+ # The value may also be a Proc, which returns an expression that evaluates to
95
+ # true (indicating that the daemon can be connected to) or false (failure).
96
+ # If the Proc raises Errno::ECONNREFUSED, Errno::ENETUNREACH, Errno::ETIMEDOUT
97
+ # Errno::ECONNRESET, Errno::EINVAL or Errno::EADDRNOTAVAIL then that also
98
+ # means that the daemon cannot be connected to.
99
+ # <b>NOTE:</b> if the ping command returns an object which responds to
100
+ # <tt>#close</tt>, then that method will be called on it.
101
+ # This makes it possible to specify a ping command such as
102
+ # <tt>lambda { TCPSocket.new('localhost', 1234) }</tt>, without having to worry
103
+ # about closing it afterwards.
104
+ # Any exceptions raised by #close are ignored.
105
+ #
106
+ # [:pid_file]
107
+ # The PID file that the daemon will write to. Used to check whether the daemon
108
+ # is running.
109
+ #
110
+ # [:lock_file]
111
+ # The lock file to use for serializing concurrent daemon management operations.
112
+ # Defaults to "(filename of PID file).lock".
113
+ #
114
+ # [:log_file]
115
+ # The log file that the daemon will write to. It will be consulted to see
116
+ # whether the daemon has printed any error messages during startup.
117
+ #
118
+ # === Optional options
119
+ # [:stop_command]
120
+ # A command to stop the daemon with, e.g. "/etc/rc.d/nginx stop". If no stop
121
+ # command is given (i.e. +nil+), then DaemonController will stop the daemon
122
+ # by killing the PID written in the PID file.
123
+ #
124
+ # The default value is +nil+.
125
+ #
126
+ # [:restart_command]
127
+ # A command to restart the daemon with, e.g. "/etc/rc.d/nginx restart". If
128
+ # no restart command is given (i.e. +nil+), then DaemonController will
129
+ # restart the daemon by calling #stop and #start.
130
+ #
131
+ # The default value is +nil+.
132
+ #
133
+ # [:before_start]
134
+ # This may be a Proc. It will be called just before running the start command.
135
+ # The before_start proc is not subject to the start timeout.
136
+ #
137
+ # [:start_timeout]
138
+ # The maximum amount of time, in seconds, that #start may take to start
139
+ # the daemon. Since #start also waits until the daemon can be connected to,
140
+ # that wait time is counted as well. If the daemon does not start in time,
141
+ # then #start will raise an exception.
142
+ #
143
+ # The default value is 15.
144
+ #
145
+ # [:stop_timeout]
146
+ # The maximum amount of time, in seconds, that #stop may take to stop
147
+ # the daemon. Since #stop also waits until the daemon is no longer running,
148
+ # that wait time is counted as well. If the daemon does not stop in time,
149
+ # then #stop will raise an exception.
150
+ #
151
+ # The default value is 15.
152
+ #
153
+ # [:log_file_activity_timeout]
154
+ # Once a daemon has gone into the background, it will become difficult to
155
+ # know for certain whether it is still initializing or whether it has
156
+ # failed and exited, until it has written its PID file. Suppose that it
157
+ # failed with an error after daemonizing but before it has written its PID file;
158
+ # not many system administrators want to wait 15 seconds (the default start
159
+ # timeout) to be notified of whether the daemon has terminated with an error.
160
+ #
161
+ # An alternative way to check whether the daemon has terminated with an error,
162
+ # is by checking whether its log file has been recently updated. If, after the
163
+ # daemon has started, the log file hasn't been updated for the amount of seconds
164
+ # given by the :log_file_activity_timeout option, then the daemon is assumed to
165
+ # have terminated with an error.
166
+ #
167
+ # The default value is 7.
168
+ #
169
+ # [:daemonize_for_me]
170
+ # Normally daemon_controller will wait until the daemon has daemonized into the
171
+ # background, in order to capture any errors that it may print on stdout or
172
+ # stderr before daemonizing. However, if the daemon doesn't support daemonization
173
+ # for some reason, then setting this option to true will cause daemon_controller
174
+ # to do the daemonization for the daemon.
175
+ #
176
+ # The default is false.
177
+ #
178
+ # [:keep_ios]
179
+ # Upon spawning the daemon, daemon_controller will normally close all file
180
+ # descriptors except stdin, stdout and stderr. However if there are any file
181
+ # descriptors you want to keep open, specify the IO objects here. This must be
182
+ # an array of IO objects.
183
+ #
184
+ # [:env]
185
+ # This must be a Hash. The hash will contain the environment variables available
186
+ # to be made available to the daemon. Hash keys must be strings, not symbols.
187
+ def initialize(options)
188
+ [:identifier, :start_command, :ping_command, :pid_file, :log_file].each do |option|
189
+ if !options.has_key?(option)
190
+ raise ArgumentError, "The ':#{option}' option is mandatory."
191
+ end
192
+ end
193
+ @identifier = options[:identifier]
194
+ @start_command = options[:start_command]
195
+ @stop_command = options[:stop_command]
196
+ @ping_command = options[:ping_command]
197
+ @restart_command = options[:restart_command]
198
+ @ping_interval = options[:ping_interval] || 0.1
199
+ @pid_file = options[:pid_file]
200
+ @log_file = options[:log_file]
201
+ @before_start = options[:before_start]
202
+ @start_timeout = options[:start_timeout] || 15
203
+ @stop_timeout = options[:stop_timeout] || 15
204
+ @log_file_activity_timeout = options[:log_file_activity_timeout] || 7
205
+ @daemonize_for_me = options[:daemonize_for_me]
206
+ @keep_ios = options[:keep_ios] || []
207
+ @lock_file = determine_lock_file(options, @identifier, @pid_file)
208
+ @env = options[:env] || {}
209
+ end
210
+
211
+ # Start the daemon and wait until it can be pinged.
212
+ #
213
+ # Raises:
214
+ # - AlreadyStarted - the daemon is already running.
215
+ # - StartError - the start command failed.
216
+ # - StartTimeout - the daemon did not start in time. This could also
217
+ # mean that the daemon failed after it has gone into the background.
218
+ def start
219
+ @lock_file.exclusive_lock do
220
+ start_without_locking
221
+ end
222
+ end
223
+
224
+ # Connect to the daemon by running the given block, which contains the
225
+ # connection logic. If the daemon isn't already running, then it will be
226
+ # started.
227
+ #
228
+ # The block must return nil or raise Errno::ECONNREFUSED, Errno::ENETUNREACH,
229
+ # Errno::ETIMEDOUT, Errno::ECONNRESET, Errno::EINVAL and Errno::EADDRNOTAVAIL
230
+ # to indicate that the daemon cannot be
231
+ # connected to. It must return non-nil if the daemon can be connected to.
232
+ # Upon successful connection, the return value of the block will
233
+ # be returned by #connect.
234
+ #
235
+ # Note that the block may be called multiple times.
236
+ #
237
+ # Raises:
238
+ # - StartError - an attempt to start the daemon was made, but the start
239
+ # command failed with an error.
240
+ # - StartTimeout - an attempt to start the daemon was made, but the daemon
241
+ # did not start in time, or it failed after it has gone into the background.
242
+ # - ConnectError - the daemon wasn't already running, but we couldn't connect
243
+ # to the daemon even after starting it.
244
+ def connect
245
+ connection = nil
246
+ @lock_file.shared_lock do
247
+ begin
248
+ connection = yield
249
+ rescue *ALLOWED_CONNECT_EXCEPTIONS
250
+ connection = nil
251
+ end
252
+ end
253
+ if connection.nil?
254
+ @lock_file.exclusive_lock do
255
+ if !daemon_is_running?
256
+ start_without_locking
257
+ end
258
+ connect_exception = nil
259
+ begin
260
+ connection = yield
261
+ rescue *ALLOWED_CONNECT_EXCEPTIONS => e
262
+ connection = nil
263
+ connect_exception = e
264
+ end
265
+ if connection.nil?
266
+ # Daemon is running but we couldn't connect to it. Possible
267
+ # reasons:
268
+ # - The daemon froze.
269
+ # - Bizarre security restrictions.
270
+ # - There's a bug in the yielded code.
271
+ if connect_exception
272
+ raise ConnectError, "Cannot connect to the daemon: #{connect_exception} (#{connect_exception.class})"
273
+ else
274
+ raise ConnectError, "Cannot connect to the daemon"
275
+ end
276
+ else
277
+ connection
278
+ end
279
+ end
280
+ else
281
+ connection
282
+ end
283
+ end
284
+
285
+ # Stop the daemon and wait until it has exited.
286
+ #
287
+ # Raises:
288
+ # - StopError - the stop command failed.
289
+ # - StopTimeout - the daemon didn't stop in time.
290
+ def stop
291
+ @lock_file.exclusive_lock do
292
+ begin
293
+ Timeout.timeout(@stop_timeout, Timeout::Error) do
294
+ kill_daemon
295
+ wait_until do
296
+ !daemon_is_running?
297
+ end
298
+ end
299
+ rescue Timeout::Error
300
+ raise StopTimeout, "Daemon '#{@identifier}' did not exit in time"
301
+ end
302
+ end
303
+ end
304
+
305
+ # Restarts the daemon. Uses the restart_command if provided, otherwise
306
+ # calls #stop and #start.
307
+ def restart
308
+ if @restart_command
309
+ run_command(@restart_command)
310
+ else
311
+ stop
312
+ start
313
+ end
314
+ end
315
+
316
+ # Returns the daemon's PID, as reported by its PID file. Returns the PID
317
+ # as an integer, or nil there is no valid PID in the PID file.
318
+ #
319
+ # This method doesn't check whether the daemon's actually running.
320
+ # Use #running? if you want to check whether it's actually running.
321
+ #
322
+ # Raises SystemCallError or IOError if something went wrong during
323
+ # reading of the PID file.
324
+ def pid
325
+ @lock_file.shared_lock do
326
+ read_pid_file
327
+ end
328
+ end
329
+
330
+ # Checks whether the daemon is still running. This is done by reading
331
+ # the PID file and then checking whether there is a process with that
332
+ # PID.
333
+ #
334
+ # Raises SystemCallError or IOError if something went wrong during
335
+ # reading of the PID file.
336
+ def running?
337
+ @lock_file.shared_lock do
338
+ daemon_is_running?
339
+ end
340
+ end
341
+
342
+ # Checks whether ping Unix domain sockets is supported. Currently
343
+ # this is supported on all Ruby implementations, except JRuby.
344
+ def self.can_ping_unix_sockets?
345
+ RUBY_PLATFORM != "java"
346
+ end
347
+
348
+ private
349
+ def start_without_locking
350
+ if daemon_is_running?
351
+ raise AlreadyStarted, "Daemon '#{@identifier}' is already started"
352
+ end
353
+ save_log_file_information
354
+ delete_pid_file
355
+ begin
356
+ started = false
357
+ before_start
358
+ Timeout.timeout(@start_timeout, Timeout::Error) do
359
+ done = false
360
+ spawn_daemon
361
+ record_activity
362
+
363
+ # We wait until the PID file is available and until
364
+ # the daemon responds to pings, but we wait no longer
365
+ # than @start_timeout seconds in total (including daemon
366
+ # spawn time).
367
+ # Furthermore, if the log file hasn't changed for
368
+ # @log_file_activity_timeout seconds, and the PID file
369
+ # still isn't available or the daemon still doesn't
370
+ # respond to pings, then assume that the daemon has
371
+ # terminated with an error.
372
+ wait_until do
373
+ if log_file_has_changed?
374
+ record_activity
375
+ elsif no_activity?(@log_file_activity_timeout)
376
+ raise Timeout::Error, "Daemon seems to have exited"
377
+ end
378
+ pid_file_available?
379
+ end
380
+ wait_until(@ping_interval) do
381
+ if log_file_has_changed?
382
+ record_activity
383
+ elsif no_activity?(@log_file_activity_timeout)
384
+ raise Timeout::Error, "Daemon seems to have exited"
385
+ end
386
+ run_ping_command || !daemon_is_running?
387
+ end
388
+ started = run_ping_command
389
+ end
390
+ result = started
391
+ rescue DaemonizationTimeout, Timeout::Error => e
392
+ start_timed_out
393
+ if pid_file_available?
394
+ kill_daemon_with_signal(true)
395
+ end
396
+ if e.is_a?(DaemonizationTimeout)
397
+ result = :daemonization_timeout
398
+ else
399
+ result = :start_timeout
400
+ end
401
+ end
402
+ if !result
403
+ raise(StartError, differences_in_log_file ||
404
+ "Daemon '#{@identifier}' failed to start.")
405
+ elsif result == :daemonization_timeout
406
+ raise(StartTimeout, differences_in_log_file ||
407
+ "Daemon '#{@identifier}' didn't daemonize in time.")
408
+ elsif result == :start_timeout
409
+ raise(StartTimeout, differences_in_log_file ||
410
+ "Daemon '#{@identifier}' failed to start in time.")
411
+ else
412
+ true
413
+ end
414
+ end
415
+
416
+ def before_start
417
+ if @before_start
418
+ @before_start.call
419
+ end
420
+ end
421
+
422
+ def spawn_daemon
423
+ if @start_command.respond_to?(:call)
424
+ run_command(@start_command.call)
425
+ else
426
+ run_command(@start_command)
427
+ end
428
+ end
429
+
430
+ def kill_daemon
431
+ if @stop_command
432
+ begin
433
+ run_command(@stop_command)
434
+ rescue StartError => e
435
+ raise StopError, e.message
436
+ end
437
+ else
438
+ kill_daemon_with_signal
439
+ end
440
+ end
441
+
442
+ def kill_daemon_with_signal(force = false)
443
+ pid = read_pid_file
444
+ if pid
445
+ if force
446
+ Process.kill('SIGKILL', pid)
447
+ else
448
+ Process.kill('SIGTERM', pid)
449
+ end
450
+ end
451
+ rescue Errno::ESRCH, Errno::ENOENT
452
+ end
453
+
454
+ def daemon_is_running?
455
+ begin
456
+ pid = read_pid_file
457
+ rescue Errno::ENOENT
458
+ # The PID file may not exist, or another thread/process
459
+ # executing #running? may have just deleted the PID file.
460
+ # So we catch this error.
461
+ pid = nil
462
+ end
463
+ if pid.nil?
464
+ false
465
+ elsif check_pid(pid)
466
+ true
467
+ else
468
+ delete_pid_file
469
+ false
470
+ end
471
+ end
472
+
473
+ def read_pid_file
474
+ pid = File.read(@pid_file).strip
475
+ if pid =~ /\A\d+\Z/
476
+ pid.to_i
477
+ else
478
+ nil
479
+ end
480
+ end
481
+
482
+ def delete_pid_file
483
+ File.unlink(@pid_file)
484
+ rescue Errno::EPERM, Errno::EACCES, Errno::ENOENT # ignore
485
+ end
486
+
487
+ def check_pid(pid)
488
+ Process.kill(0, pid)
489
+ true
490
+ rescue Errno::ESRCH
491
+ false
492
+ rescue Errno::EPERM
493
+ # We didn't have permission to kill the process. Either the process
494
+ # is owned by someone else, or the system has draconian security
495
+ # settings and we aren't allowed to kill *any* process. Assume that
496
+ # the process is running.
497
+ true
498
+ end
499
+
500
+ def wait_until(sleep_interval = 0.1)
501
+ while !yield
502
+ sleep(sleep_interval)
503
+ end
504
+ end
505
+
506
+ def wait_until_pid_file_is_available_or_log_file_has_changed
507
+ while !(pid_file_available? || log_file_has_changed?)
508
+ sleep 0.1
509
+ end
510
+ pid_file_is_available?
511
+ end
512
+
513
+ def wait_until_daemon_responds_to_ping_or_has_exited_or_log_file_has_changed
514
+ while !(run_ping_command || !daemon_is_running? || log_file_has_changed?)
515
+ sleep(@ping_interval)
516
+ end
517
+ run_ping_command
518
+ end
519
+
520
+ def record_activity
521
+ @last_activity_time = Time.now
522
+ end
523
+
524
+ # Check whether there has been no recorded activity in the past +seconds+ seconds.
525
+ def no_activity?(seconds)
526
+ Time.now - @last_activity_time > seconds
527
+ end
528
+
529
+ def pid_file_available?
530
+ File.exist?(@pid_file) && File.stat(@pid_file).size != 0
531
+ end
532
+
533
+ # This method does nothing and only serves as a hook for the unit test.
534
+ def start_timed_out
535
+ end
536
+
537
+ # This method does nothing and only serves as a hook for the unit test.
538
+ def daemonization_timed_out
539
+ end
540
+
541
+ def save_log_file_information
542
+ @original_log_file_stat = File.stat(@log_file) rescue nil
543
+ @current_log_file_stat = @original_log_file_stat
544
+ end
545
+
546
+ def log_file_has_changed?
547
+ if @current_log_file_stat
548
+ stat = File.stat(@log_file) rescue nil
549
+ if stat
550
+ result = @current_log_file_stat.mtime != stat.mtime ||
551
+ @current_log_file_stat.size != stat.size
552
+ @current_log_file_stat = stat
553
+ result
554
+ else
555
+ true
556
+ end
557
+ else
558
+ false
559
+ end
560
+ end
561
+
562
+ def differences_in_log_file
563
+ if @original_log_file_stat
564
+ File.open(@log_file, 'r') do |f|
565
+ f.seek(@original_log_file_stat.size, IO::SEEK_SET)
566
+ diff = f.read.strip
567
+ if diff.empty?
568
+ nil
569
+ else
570
+ diff
571
+ end
572
+ end
573
+ else
574
+ nil
575
+ end
576
+ rescue Errno::ENOENT, Errno::ESPIPE
577
+ # ESPIPE means the log file is a pipe.
578
+ nil
579
+ end
580
+
581
+ def determine_lock_file(options, identifier, pid_file)
582
+ if options[:lock_file]
583
+ LockFile.new(File.expand_path(options[:lock_file]))
584
+ else
585
+ LockFile.new(File.expand_path(pid_file + ".lock"))
586
+ end
587
+ end
588
+
589
+ def self.fork_supported?
590
+ RUBY_PLATFORM != "java" && RUBY_PLATFORM !~ /win32/
591
+ end
592
+
593
+ def self.spawn_supported?
594
+ # Process.spawn doesn't work very well in JRuby.
595
+ Process.respond_to?(:spawn) && RUBY_PLATFORM != "java"
596
+ end
597
+
598
+ def run_command(command)
599
+ # Create tempfile for storing the command's output.
600
+ tempfile = Tempfile.new('daemon-output')
601
+ tempfile_path = tempfile.path
602
+ File.chmod(0666, tempfile_path)
603
+ tempfile.close
604
+
605
+ if self.class.fork_supported? || self.class.spawn_supported?
606
+ if Process.respond_to?(:spawn)
607
+ options = {
608
+ :in => "/dev/null",
609
+ :out => tempfile_path,
610
+ :err => tempfile_path,
611
+ :close_others => true
612
+ }
613
+ @keep_ios.each do |io|
614
+ options[io] = io
615
+ end
616
+ if @daemonize_for_me
617
+ pid = Process.spawn(@env, ruby_interpreter, SPAWNER_FILE,
618
+ command, options)
619
+ else
620
+ pid = Process.spawn(@env, command, options)
621
+ end
622
+ else
623
+ pid = safe_fork(@daemonize_for_me) do
624
+ ObjectSpace.each_object(IO) do |obj|
625
+ if !@keep_ios.include?(obj)
626
+ obj.close rescue nil
627
+ end
628
+ end
629
+ STDIN.reopen("/dev/null", "r")
630
+ STDOUT.reopen(tempfile_path, "w")
631
+ STDERR.reopen(tempfile_path, "w")
632
+ ENV.update(@env)
633
+ exec(command)
634
+ end
635
+ end
636
+
637
+ # run_command might be running in a timeout block (like
638
+ # in #start_without_locking).
639
+ begin
640
+ interruptable_waitpid(pid)
641
+ rescue Errno::ECHILD
642
+ # Maybe a background thread or whatever waitpid()'ed
643
+ # this child process before we had the chance. There's
644
+ # no way to obtain the exit status now. Assume that
645
+ # it started successfully; if it didn't we'll know
646
+ # that later by checking the PID file and by pinging
647
+ # it.
648
+ return
649
+ rescue Timeout::Error
650
+ daemonization_timed_out
651
+
652
+ # If the daemon doesn't fork into the background
653
+ # in time, then kill it.
654
+ begin
655
+ Process.kill('SIGTERM', pid)
656
+ rescue SystemCallError
657
+ end
658
+ begin
659
+ Timeout.timeout(5, Timeout::Error) do
660
+ begin
661
+ interruptable_waitpid(pid)
662
+ rescue SystemCallError
663
+ end
664
+ end
665
+ rescue Timeout::Error
666
+ begin
667
+ Process.kill('SIGKILL', pid)
668
+ interruptable_waitpid(pid)
669
+ rescue SystemCallError
670
+ end
671
+ end
672
+ raise DaemonizationTimeout
673
+ end
674
+ if $?.exitstatus != 0
675
+ raise StartError, File.read(tempfile_path).strip
676
+ end
677
+ else
678
+ if @env && !@env.empty?
679
+ raise "Setting the :env option is not supported on this Ruby implementation."
680
+ elsif @daemonize_for_me
681
+ raise "Setting the :daemonize_for_me option is not supported on this Ruby implementation."
682
+ end
683
+
684
+ cmd = "#{command} >\"#{tempfile_path}\""
685
+ cmd << " 2>\"#{tempfile_path}\"" unless PLATFORM =~ /mswin/
686
+ if !system(cmd)
687
+ raise StartError, File.read(tempfile_path).strip
688
+ end
689
+ end
690
+ ensure
691
+ File.unlink(tempfile_path) rescue nil
692
+ end
693
+
694
+ def run_ping_command
695
+ if @ping_command.respond_to?(:call)
696
+ begin
697
+ value = @ping_command.call
698
+ if value.respond_to?(:close)
699
+ value.close rescue nil
700
+ end
701
+ value
702
+ rescue *ALLOWED_CONNECT_EXCEPTIONS
703
+ false
704
+ end
705
+ elsif @ping_command.is_a?(Array)
706
+ type, *args = @ping_command
707
+ if self.class.can_ping_unix_sockets?
708
+ case type
709
+ when :tcp
710
+ hostname, port = args
711
+ sockaddr = Socket.pack_sockaddr_in(port, hostname)
712
+ ping_tcp_socket(sockaddr)
713
+ when :unix
714
+ socket_domain = Socket::Constants::AF_LOCAL
715
+ sockaddr = Socket.pack_sockaddr_un(args[0])
716
+ ping_socket(socket_domain, sockaddr)
717
+ else
718
+ raise ArgumentError, "Unknown ping command type #{type.inspect}"
719
+ end
720
+ else
721
+ case type
722
+ when :tcp
723
+ hostname, port = args
724
+ ping_socket(hostname, port)
725
+ when :unix
726
+ raise "Pinging Unix domain sockets is not supported on this Ruby implementation"
727
+ else
728
+ raise ArgumentError, "Unknown ping command type #{type.inspect}"
729
+ end
730
+ end
731
+ else
732
+ system(@ping_command)
733
+ end
734
+ end
735
+
736
+ if !can_ping_unix_sockets?
737
+ require 'java'
738
+
739
+ def ping_socket(host_name, port)
740
+ channel = java.nio.channels.SocketChannel.open
741
+ begin
742
+ address = java.net.InetSocketAddress.new(host_name, port)
743
+ channel.configure_blocking(false)
744
+ if channel.connect(address)
745
+ return true
746
+ end
747
+
748
+ deadline = Time.now.to_f + 0.1
749
+ done = false
750
+ while true
751
+ begin
752
+ if channel.finish_connect
753
+ return true
754
+ end
755
+ rescue java.net.ConnectException => e
756
+ if e.message =~ /Connection refused/i
757
+ return false
758
+ else
759
+ throw e
760
+ end
761
+ end
762
+
763
+ # Not done connecting and no error.
764
+ sleep 0.01
765
+ if Time.now.to_f >= deadline
766
+ return false
767
+ end
768
+ end
769
+ ensure
770
+ channel.close
771
+ end
772
+ end
773
+ else
774
+ def ping_socket(socket_domain, sockaddr)
775
+ begin
776
+ socket = Socket.new(socket_domain, Socket::Constants::SOCK_STREAM, 0)
777
+ begin
778
+ socket.connect_nonblock(sockaddr)
779
+ rescue Errno::ENOENT, Errno::EINPROGRESS, Errno::EAGAIN, Errno::EWOULDBLOCK
780
+ if select(nil, [socket], nil, 0.1)
781
+ begin
782
+ socket.connect_nonblock(sockaddr)
783
+ rescue Errno::EISCONN
784
+ rescue Errno::EINVAL
785
+ if RUBY_PLATFORM =~ /freebsd/i
786
+ raise Errno::ECONNREFUSED
787
+ else
788
+ raise
789
+ end
790
+ end
791
+ else
792
+ raise Errno::ECONNREFUSED
793
+ end
794
+ end
795
+ true
796
+ rescue Errno::ECONNREFUSED, Errno::ENOENT
797
+ false
798
+ ensure
799
+ socket.close if socket
800
+ end
801
+ end
802
+
803
+ def ping_tcp_socket(sockaddr)
804
+ begin
805
+ ping_socket(Socket::Constants::AF_INET, sockaddr)
806
+ rescue Errno::EAFNOSUPPORT
807
+ ping_socket(Socket::Constants::AF_INET6, sockaddr)
808
+ end
809
+ end
810
+ end
811
+
812
+ def ruby_interpreter
813
+ if defined?(RbConfig)
814
+ rb_config = RbConfig::CONFIG
815
+ else
816
+ rb_config = Config::CONFIG
817
+ end
818
+ File.join(
819
+ rb_config['bindir'],
820
+ rb_config['RUBY_INSTALL_NAME']
821
+ ) + rb_config['EXEEXT']
822
+ end
823
+
824
+ def safe_fork(double_fork)
825
+ pid = fork
826
+ if pid.nil?
827
+ begin
828
+ if double_fork
829
+ pid2 = fork
830
+ if pid2.nil?
831
+ Process.setsid
832
+ yield
833
+ end
834
+ else
835
+ yield
836
+ end
837
+ rescue Exception => e
838
+ message = "*** Exception #{e.class} " <<
839
+ "(#{e}) (process #{$$}):\n" <<
840
+ "\tfrom " << e.backtrace.join("\n\tfrom ")
841
+ STDERR.write(e)
842
+ STDERR.flush
843
+ exit!
844
+ ensure
845
+ exit!(0)
846
+ end
847
+ else
848
+ if double_fork
849
+ Process.waitpid(pid) rescue nil
850
+ pid
851
+ else
852
+ pid
853
+ end
854
+ end
855
+ end
856
+
857
+ if RUBY_VERSION < "1.9"
858
+ def interruptable_waitpid(pid)
859
+ Process.waitpid(pid)
860
+ end
861
+ else
862
+ # On Ruby 1.9, Thread#kill (which is called by timeout.rb) may
863
+ # not be able to interrupt Process.waitpid. So here we use a
864
+ # special version that's a bit less efficient but is at least
865
+ # interruptable.
866
+ def interruptable_waitpid(pid)
867
+ result = nil
868
+ while !result
869
+ result = Process.waitpid(pid, Process::WNOHANG)
870
+ sleep 0.01 if !result
871
+ end
872
+ result
873
+ end
874
+ end
875
+ end
876
+
877
+ end # module PhusionPassenger