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
@@ -36,582 +36,572 @@ PhusionPassenger.require_passenger_lib 'request_handler/thread_handler'
36
36
 
37
37
  module PhusionPassenger
38
38
 
39
-
40
- class RequestHandler
41
- include DebugLogging
42
- include Utils
43
-
44
- # Signal which will cause the application to exit immediately.
45
- HARD_TERMINATION_SIGNAL = "SIGTERM"
46
- BACKLOG_SIZE = 500
47
-
48
- # String constants which exist to relieve Ruby's garbage collector.
49
- IGNORE = 'IGNORE' # :nodoc:
50
- DEFAULT = 'DEFAULT' # :nodoc:
51
-
52
- # A hash containing all server sockets that this request handler listens on.
53
- # The hash is in the form of:
54
- #
55
- # {
56
- # name1 => [socket_address1, socket_type1, socket1],
57
- # name2 => [socket_address2, socket_type2, socket2],
58
- # ...
59
- # }
60
- #
61
- # +name+ is a Symbol. +socket_addressx+ is the address of the socket,
62
- # +socket_typex+ is the socket's type (either 'unix' or 'tcp') and
63
- # +socketx+ is the actual socket IO objec.
64
- # There's guaranteed to be at least one server socket, namely one with the
65
- # name +:main+.
66
- attr_reader :server_sockets
67
-
68
- attr_reader :concurrency
69
-
70
- # A password with which clients must authenticate. Default is unauthenticated.
71
- attr_accessor :connect_password
72
-
73
- # Create a new RequestHandler with the given owner pipe.
74
- # +owner_pipe+ must be the readable part of a pipe IO object.
75
- #
76
- # Additionally, the following options may be given:
77
- # - connect_password
78
- def initialize(owner_pipe, options = {})
79
- require_option(options, "app_group_name")
80
- install_options_as_ivars(self, options,
81
- "app",
82
- "app_group_name",
83
- "connect_password",
84
- "union_station_core"
85
- )
86
-
87
- @keepalive = options.fetch("keepalive", true).to_s == "true"
88
- @force_http_session = ENV["_PASSENGER_FORCE_HTTP_SESSION"] == "true"
89
- if @force_http_session
90
- @connect_password = nil
91
- end
92
- @thread_handler = options["thread_handler"] || ThreadHandler
93
- @concurrency = 1
94
-
95
- #############
96
- #############
97
-
98
- @server_sockets = {}
99
-
100
- if should_use_unix_sockets?
101
- @main_socket_address, @main_socket = create_unix_socket_on_filesystem(options)
102
- else
103
- @main_socket_address, @main_socket = create_tcp_socket
104
- end
105
- @server_sockets[:main] = {
106
- :address => @main_socket_address,
107
- :socket => @main_socket,
108
- :protocol => @force_http_session ? :http_session : :session,
109
- :concurrency => @concurrency
110
- }
111
-
112
- @http_socket_address, @http_socket = create_tcp_socket
113
- @server_sockets[:http] = {
114
- :address => @http_socket_address,
115
- :socket => @http_socket,
116
- :protocol => :http,
117
- :concurrency => 1
118
- }
119
-
120
- @owner_pipe = owner_pipe
121
- @options = options
122
- @previous_signal_handlers = {}
123
- @main_loop_generation = 0
124
- @main_loop_thread_lock = Mutex.new
125
- @main_loop_thread_cond = ConditionVariable.new
126
- @threads = []
127
- @threads_mutex = Mutex.new
128
- @main_loop_running = false
129
-
130
- #############
131
- end
132
-
133
- # Clean up temporary stuff created by the request handler.
134
- #
135
- # If the main loop was started by #main_loop, then this method may only
136
- # be called after the main loop has exited.
137
- #
138
- # If the main loop was started by #start_main_loop_thread, then this method
139
- # may be called at any time, and it will stop the main loop thread.
140
- def cleanup
141
- if @main_loop_thread
142
- @main_loop_thread_lock.synchronize do
143
- @graceful_termination_pipe[1].close rescue nil
144
- end
145
- @main_loop_thread.join
146
- end
147
- @server_sockets.each_value do |info|
148
- socket = info[:socket]
149
- type = get_socket_address_type(info[:address])
150
-
151
- begin
152
- socket.close if !socket.closed?
153
- rescue Exception => e
154
- # Ignore "stream closed" error, which occurs in some unit tests.
155
- # We catch Exception here instead of IOError because of a Ruby 1.8.7 bug.
156
- if e.to_s !~ /stream closed/ && e.message.to_s !~ /stream closed/
157
- raise e
158
- end
159
- end
160
- if type == :unix
161
- filename = info[:address].sub(/^unix:/, '')
162
- File.unlink(filename) rescue nil
163
- end
164
- end
165
- @owner_pipe.close rescue nil
166
- end
167
-
168
- # Check whether the main loop's currently running.
169
- def main_loop_running?
170
- @main_loop_thread_lock.synchronize do
171
- return @main_loop_running
172
- end
173
- end
174
-
175
- # Enter the request handler's main loop.
176
- def main_loop
177
- debug("Entering request handler main loop")
178
- reset_signal_handlers
179
- begin
180
- @graceful_termination_pipe = IO.pipe
181
- @graceful_termination_pipe[0].close_on_exec!
182
- @graceful_termination_pipe[1].close_on_exec!
183
-
184
- @main_loop_thread_lock.synchronize do
185
- @main_loop_generation += 1
186
- @main_loop_running = true
187
- @main_loop_thread_cond.broadcast
188
-
189
- @select_timeout = nil
190
-
191
- @selectable_sockets = []
192
- @server_sockets.each_value do |value|
193
- socket = value[2]
194
- @selectable_sockets << socket if socket
195
- end
196
- @selectable_sockets << @owner_pipe
197
- @selectable_sockets << @graceful_termination_pipe[0]
198
- end
199
-
200
- install_useful_signal_handlers
201
- start_threads
202
- wait_until_termination_requested
203
- wait_until_all_threads_are_idle
204
- terminate_threads
205
- debug("Request handler main loop exited normally")
206
-
207
- rescue EOFError
208
- # Exit main loop.
209
- trace(2, "Request handler main loop interrupted by EOFError exception")
210
- rescue Interrupt
211
- # Exit main loop.
212
- trace(2, "Request handler main loop interrupted by Interrupt exception")
213
- rescue SignalException => signal
214
- trace(2, "Request handler main loop interrupted by SignalException")
215
- if signal.message != HARD_TERMINATION_SIGNAL
216
- raise
217
- end
218
- rescue Exception => e
219
- trace(2, "Request handler main loop interrupted by #{e.class} exception")
220
- raise
221
- ensure
222
- debug("Exiting request handler main loop")
223
- revert_signal_handlers
224
- @main_loop_thread_lock.synchronize do
225
- @graceful_termination_pipe[1].close rescue nil
226
- @graceful_termination_pipe[0].close rescue nil
227
- @selectable_sockets = []
228
- @main_loop_generation += 1
229
- @main_loop_running = false
230
- @main_loop_thread_cond.broadcast
231
- end
232
- end
233
- end
234
-
235
- # Start the main loop in a new thread. This thread will be stopped by #cleanup.
236
- def start_main_loop_thread
237
- current_generation = @main_loop_generation
238
- @main_loop_thread = Thread.new do
239
- begin
240
- main_loop
241
- rescue Exception => e
242
- print_exception(self.class, e)
243
- end
244
- end
245
- @main_loop_thread_lock.synchronize do
246
- while @main_loop_generation == current_generation
247
- @main_loop_thread_cond.wait(@main_loop_thread_lock)
248
- end
249
- end
250
- end
251
-
252
- private
253
- def should_use_unix_sockets?
254
- # Historical note:
255
- # There seems to be a bug in MacOS X Leopard w.r.t. Unix server
256
- # sockets file descriptors that are passed to another process.
257
- # Usually Unix server sockets work fine, but when they're passed
258
- # to another process, then clients that connect to the socket
259
- # can incorrectly determine that the client socket is closed,
260
- # even though that's not actually the case. More specifically:
261
- # recv()/read() calls on these client sockets can return 0 even
262
- # when we know EOF is not reached.
263
- #
264
- # The ApplicationPool infrastructure used to connect to a backend
265
- # process's Unix socket in the helper server process, and then
266
- # pass the connection file descriptor to the web server, which
267
- # triggers this kernel bug. We used to work around this by using
268
- # TCP sockets instead of Unix sockets; TCP sockets can still fail
269
- # with this fake-EOF bug once in a while, but not nearly as often
270
- # as with Unix sockets.
271
- #
272
- # This problem no longer applies today. The web server now passes
273
- # all I/O through the HelperAgent, and the bug is no longer
274
- # triggered. Nevertheless, we keep this function intact so that
275
- # if something like this ever happens again, we know why, and we
276
- # can easily reactivate the workaround. Or maybe if we just need
277
- # TCP sockets for some other reason.
278
-
279
- #return RUBY_PLATFORM !~ /darwin/
280
-
281
- ruby_engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby"
282
- # Unix domain socket implementation on JRuby
283
- # is still bugged as of version 1.7.0. They can
284
- # cause unexplicable freezes when used in combination
285
- # with threading.
286
- return !@force_http_session && ruby_engine != "jruby"
287
- end
288
-
289
- def create_unix_socket_on_filesystem(options)
290
- if defined?(NativeSupport)
291
- unix_path_max = NativeSupport::UNIX_PATH_MAX
292
- else
293
- unix_path_max = options.fetch('UNIX_PATH_MAX', 100).to_i
294
- end
295
- if options['socket_dir']
296
- socket_dir = options['socket_dir']
297
- socket_prefix = "ruby"
298
- else
299
- socket_dir = Dir.tmpdir
300
- socket_prefix = "PsgRubyApp"
301
- end
302
-
303
- retry_at_most(128, Errno::EADDRINUSE) do
304
- socket_address = "#{socket_dir}/#{socket_prefix}.#{generate_random_id(:base64)}"
305
- socket_address = socket_address.slice(0, unix_path_max - 10)
306
- socket = UNIXServer.new(socket_address)
307
- socket.listen(BACKLOG_SIZE)
308
- socket.binmode
309
- socket.sync = true
310
- socket.close_on_exec!
311
- File.chmod(0600, socket_address)
312
- ["unix:#{socket_address}", socket]
313
- end
314
- end
315
-
316
- def create_tcp_socket
317
- # We use "127.0.0.1" as address in order to force
318
- # TCPv4 instead of TCPv6.
319
- socket = TCPServer.new('127.0.0.1', 0)
320
- socket.listen(BACKLOG_SIZE)
321
- socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
322
- socket.binmode
323
- socket.sync = true
324
- socket.close_on_exec!
325
- socket_address = "tcp://127.0.0.1:#{socket.addr[1]}"
326
- return [socket_address, socket]
327
- end
328
-
329
- # Reset signal handlers to their default handler, and install some
330
- # special handlers for a few signals. The previous signal handlers
331
- # will be put back by calling revert_signal_handlers.
332
- def reset_signal_handlers
333
- Signal.list_trappable.each_key do |signal|
334
- begin
335
- prev_handler = trap(signal, DEFAULT)
336
- if prev_handler != DEFAULT
337
- @previous_signal_handlers[signal] = prev_handler
338
- end
339
- rescue ArgumentError
340
- # Signal cannot be trapped; ignore it.
341
- end
342
- end
343
- trap('HUP', IGNORE)
344
- PhusionPassenger.call_event(:after_installing_signal_handlers)
345
- end
346
-
347
- def install_useful_signal_handlers
348
- trappable_signals = Signal.list_trappable
349
-
350
- trap('ABRT') do
351
- print_status_report
352
- end if trappable_signals.has_key?('ABRT')
353
- trap('QUIT') do
354
- print_status_report
355
- end if trappable_signals.has_key?('QUIT')
356
- end
357
-
358
- def revert_signal_handlers
359
- @previous_signal_handlers.each_pair do |signal, handler|
360
- trap(signal, handler)
361
- end
362
- end
363
-
364
- def print_status_report
365
- warn(Utils.global_backtrace_report)
366
- warn("Threads: #{@threads.inspect}")
367
- end
368
-
369
- def start_threads
370
- common_options = {
371
- :app => @app,
372
- :app_group_name => @app_group_name,
373
- :connect_password => @connect_password,
374
- :union_station_core => @union_station_core,
375
- :keepalive_enabled => @keepalive
376
- }
377
- main_socket_options = common_options.merge(
378
- :server_socket => @main_socket,
379
- :socket_name => "main socket",
380
- :protocol => @server_sockets[:main][:protocol] == :session ?
381
- :session :
382
- :http
383
- )
384
- http_socket_options = common_options.merge(
385
- :server_socket => @http_socket,
386
- :socket_name => "HTTP socket",
387
- :protocol => :http
388
- )
389
-
390
- # Used for marking threads that have finished initializing,
391
- # or failed during initialization. Threads that are not yet done
392
- # are not in `initialization_state`. Threads that have succeeded
393
- # set their own state to true. Threads that have failed set their
394
- # own state to false.
395
- initialization_state_mutex = Mutex.new
396
- initialization_state_cond = ConditionVariable.new
397
- initialization_state = {}
398
- set_initialization_state = lambda do |value|
399
- initialization_state_mutex.synchronize do
400
- initialization_state[Thread.current] = value
401
- initialization_state_cond.signal
402
- end
403
- end
404
- set_initialization_state_to_true = lambda do
405
- set_initialization_state.call(true)
406
- end
407
-
408
- # Actually start all the threads.
409
- thread_handler = @thread_handler
410
- expected_nthreads = 0
411
-
412
- @threads_mutex.synchronize do
413
- @concurrency.times do |i|
414
- thread = Thread.new(i) do |number|
415
- Thread.current.abort_on_exception = true
416
- begin
417
- Thread.current[:name] = "Worker #{number + 1}"
418
- handler = thread_handler.new(self, main_socket_options)
419
- handler.install
420
- handler.main_loop(set_initialization_state_to_true)
421
- ensure
422
- set_initialization_state.call(false)
423
- unregister_current_thread
424
- end
425
- end
426
- @threads << thread
427
- expected_nthreads += 1
428
- end
429
-
430
- thread = Thread.new do
431
- Thread.current.abort_on_exception = true
432
- begin
433
- Thread.current[:name] = "HTTP helper worker"
434
- handler = thread_handler.new(self, http_socket_options)
435
- handler.install
436
- handler.main_loop(set_initialization_state_to_true)
437
- ensure
438
- set_initialization_state.call(false)
439
- unregister_current_thread
440
- end
441
- end
442
- @threads << thread
443
- expected_nthreads += 1
444
- end
445
-
446
- # Wait until all threads have finished starting.
447
- initialization_state_mutex.synchronize do
448
- while initialization_state.size != expected_nthreads
449
- initialization_state_cond.wait(initialization_state_mutex)
450
- end
451
- end
452
- end
453
-
454
- def unregister_current_thread
455
- @threads_mutex.synchronize do
456
- @threads.delete(Thread.current)
457
- end
458
- end
459
-
460
- def wait_until_termination_requested
461
- ruby_engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby"
462
- if ruby_engine == "jruby"
463
- # On JRuby, selecting on an input TTY always returns, so
464
- # we use threads to do the job.
465
- owner_pipe_watcher = IO.pipe
466
- owner_pipe_watcher_thread = Thread.new do
467
- Thread.current.abort_on_exception = true
468
- Thread.current[:name] = "Owner pipe waiter"
469
- begin
470
- @owner_pipe.read(1)
471
- ensure
472
- owner_pipe_watcher[1].write('x')
473
- end
474
- end
475
- begin
476
- ios = select([owner_pipe_watcher[0], @graceful_termination_pipe[0]])[0]
477
- if ios.include?(owner_pipe_watcher[0])
478
- trace(2, "Owner pipe closed")
479
- else
480
- trace(2, "Graceful termination pipe closed")
481
- end
482
- ensure
483
- owner_pipe_watcher_thread.kill
484
- owner_pipe_watcher_thread.join
485
- owner_pipe_watcher[0].close if !owner_pipe_watcher[0].closed?
486
- owner_pipe_watcher[1].close if !owner_pipe_watcher[1].closed?
487
- end
488
- else
489
- ios = select([@owner_pipe, @graceful_termination_pipe[0]])[0]
490
- if ios.include?(@owner_pipe)
491
- trace(2, "Owner pipe closed")
492
- else
493
- trace(2, "Graceful termination pipe closed")
494
- end
495
- end
496
- end
497
-
498
- def wakeup_all_threads
499
- threads = []
500
- if get_socket_address_type(@server_sockets[:main][:address]) == :unix &&
501
- !File.exist?(@server_sockets[:main][:address].sub(/^unix:/, ''))
502
- # It looks like someone deleted the Unix domain socket we listen on.
503
- # This makes it impossible to wake up the worker threads gracefully,
504
- # so we hard kill them.
505
- warn("Unix domain socket gone; force aborting all threads")
506
- @threads_mutex.synchronize do
507
- @threads.each do |thread|
508
- thread.raise(RuntimeError.new("Force abort"))
509
- end
510
- end
511
- else
512
- @concurrency.times do
513
- Thread.abort_on_exception = true
514
- threads << Thread.new(@server_sockets[:main][:address]) do |address|
515
- begin
516
- debug("Shutting down worker thread by connecting to #{address}")
517
- connect_to_server(address).close
518
- rescue Errno::ECONNREFUSED
519
- debug("Worker thread listening on #{address} already exited")
520
- rescue SystemCallError, IOError => e
521
- debug("Error shutting down worker thread (#{address}): #{e} (#{e.class})")
522
- end
523
- end
524
- end
525
- end
526
- threads << Thread.new(@server_sockets[:http][:address]) do |address|
527
- Thread.abort_on_exception = true
528
- begin
529
- debug("Shutting down HTTP thread by connecting to #{address}")
530
- connect_to_server(address).close
531
- rescue Errno::ECONNREFUSED
532
- debug("Worker thread listening on #{address} already exited")
533
- rescue SystemCallError, IOError => e
534
- debug("Error shutting down HTTP thread (#{address}): #{e} (#{e.class})")
535
- end
536
- end
537
- return threads
538
- end
539
-
540
- def terminate_threads
541
- debug("Stopping all threads")
542
- threads = @threads_mutex.synchronize do
543
- @threads.dup
544
- end
545
- threads.each do |thr|
546
- thr.raise(ThreadHandler::Interrupted.new)
547
- end
548
- threads.each do |thr|
549
- thr.join
550
- end
551
- debug("All threads stopped")
552
- end
553
-
554
- def wait_until_all_threads_are_idle
555
- debug("Waiting until all threads have become idle...")
556
-
557
- # We wait until 100 ms have passed since all handlers have become
558
- # interruptable and remained in the same iterations.
559
-
560
- done = false
561
-
562
- while !done
563
- handlers = @threads_mutex.synchronize do
564
- @threads.map do |thr|
565
- thr[:passenger_thread_handler]
566
- end
567
- end
568
- debug("There are currently #{handlers.size} threads")
569
- if handlers.empty?
570
- # There are no threads, so we're done.
571
- done = true
572
- break
573
- end
574
-
575
- # Record initial state.
576
- handlers.each { |h| h.stats_mutex.lock }
577
- iterations = handlers.map { |h| h.iteration }
578
- handlers.each { |h| h.stats_mutex.unlock }
579
-
580
- start_time = Time.now
581
- sleep 0.01
582
-
583
- while true
584
- if handlers.size != @threads_mutex.synchronize { @threads.size }
585
- debug("The number of threads changed. Restarting waiting algorithm")
586
- break
587
- end
588
-
589
- # Record current state.
590
- handlers.each { |h| h.stats_mutex.lock }
591
- all_interruptable = handlers.all? { |h| h.interruptable }
592
- new_iterations = handlers.map { |h| h.iteration }
593
-
594
- # Are all threads interruptable and has there been no activity
595
- # since last time we checked?
596
- if all_interruptable && new_iterations == iterations
597
- # Yes. If enough time has passed then we're done.
598
- handlers.each { |h| h.stats_mutex.unlock }
599
- if Time.now >= start_time + 0.1
600
- done = true
601
- break
602
- end
603
- else
604
- # No. We reset the timer and check again later.
605
- handlers.each { |h| h.stats_mutex.unlock }
606
- iterations = new_iterations
607
- start_time = Time.now
608
- sleep 0.01
609
- end
610
- end
611
- end
612
-
613
- debug("All threads are now idle")
614
- end
615
- end
39
+ class RequestHandler
40
+ include DebugLogging
41
+ include Utils
42
+
43
+ # Signal which will cause the application to exit immediately.
44
+ HARD_TERMINATION_SIGNAL = "SIGTERM"
45
+ BACKLOG_SIZE = 500
46
+
47
+ # String constants which exist to relieve Ruby's garbage collector.
48
+ IGNORE = 'IGNORE' # :nodoc:
49
+ DEFAULT = 'DEFAULT' # :nodoc:
50
+
51
+ # A hash containing all server sockets that this request handler listens on.
52
+ # The hash is in the form of:
53
+ #
54
+ # {
55
+ # name1 => [socket_address1, socket_type1, socket1],
56
+ # name2 => [socket_address2, socket_type2, socket2],
57
+ # ...
58
+ # }
59
+ #
60
+ # +name+ is a Symbol. +socket_addressx+ is the address of the socket,
61
+ # +socket_typex+ is the socket's type (either 'unix' or 'tcp') and
62
+ # +socketx+ is the actual socket IO objec.
63
+ # There's guaranteed to be at least one server socket, namely one with the
64
+ # name +:main+.
65
+ attr_reader :server_sockets
66
+
67
+ attr_reader :concurrency
68
+
69
+ # A password with which clients must authenticate. Default is unauthenticated.
70
+ attr_accessor :connect_password
71
+
72
+ # Create a new RequestHandler with the given owner pipe.
73
+ # +owner_pipe+ must be the readable part of a pipe IO object.
74
+ #
75
+ # Additionally, the following options may be given:
76
+ # - connect_password
77
+ def initialize(owner_pipe, options = {})
78
+ require_option(options, "app_group_name")
79
+ install_options_as_ivars(self, options,
80
+ "app",
81
+ "app_group_name",
82
+ "connect_password",
83
+ "union_station_core"
84
+ )
85
+
86
+ @keepalive = options.fetch("keepalive", true).to_s == "true"
87
+ @force_http_session = ENV["_PASSENGER_FORCE_HTTP_SESSION"] == "true"
88
+ if @force_http_session
89
+ @connect_password = nil
90
+ end
91
+ @thread_handler = options["thread_handler"] || ThreadHandler
92
+ @concurrency = 1
93
+
94
+ #############
95
+ #############
96
+
97
+ @server_sockets = {}
98
+
99
+ if should_use_unix_sockets?
100
+ @main_socket_address, @main_socket = create_unix_socket_on_filesystem(options)
101
+ else
102
+ @main_socket_address, @main_socket = create_tcp_socket
103
+ end
104
+ @server_sockets[:main] = {
105
+ :address => @main_socket_address,
106
+ :socket => @main_socket,
107
+ :protocol => @force_http_session ? :http_session : :session,
108
+ :concurrency => @concurrency
109
+ }
110
+
111
+ @http_socket_address, @http_socket = create_tcp_socket
112
+ @server_sockets[:http] = {
113
+ :address => @http_socket_address,
114
+ :socket => @http_socket,
115
+ :protocol => :http,
116
+ :concurrency => 1
117
+ }
118
+
119
+ @owner_pipe = owner_pipe
120
+ @options = options
121
+ @previous_signal_handlers = {}
122
+ @main_loop_generation = 0
123
+ @main_loop_thread_lock = Mutex.new
124
+ @main_loop_thread_cond = ConditionVariable.new
125
+ @threads = []
126
+ @threads_mutex = Mutex.new
127
+ @main_loop_running = false
128
+
129
+ #############
130
+ end
131
+
132
+ # Clean up temporary stuff created by the request handler.
133
+ #
134
+ # If the main loop was started by #main_loop, then this method may only
135
+ # be called after the main loop has exited.
136
+ #
137
+ # If the main loop was started by #start_main_loop_thread, then this method
138
+ # may be called at any time, and it will stop the main loop thread.
139
+ def cleanup
140
+ if @main_loop_thread
141
+ @main_loop_thread_lock.synchronize do
142
+ @graceful_termination_pipe[1].close rescue nil
143
+ end
144
+ @main_loop_thread.join
145
+ end
146
+ @server_sockets.each_value do |info|
147
+ socket = info[:socket]
148
+ type = get_socket_address_type(info[:address])
149
+
150
+ begin
151
+ socket.close if !socket.closed?
152
+ rescue Exception => e
153
+ # Ignore "stream closed" error, which occurs in some unit tests.
154
+ # We catch Exception here instead of IOError because of a Ruby 1.8.7 bug.
155
+ if e.to_s !~ /stream closed/ && e.message.to_s !~ /stream closed/
156
+ raise e
157
+ end
158
+ end
159
+ if type == :unix
160
+ filename = info[:address].sub(/^unix:/, '')
161
+ File.unlink(filename) rescue nil
162
+ end
163
+ end
164
+ @owner_pipe.close rescue nil
165
+ end
166
+
167
+ # Check whether the main loop's currently running.
168
+ def main_loop_running?
169
+ @main_loop_thread_lock.synchronize do
170
+ return @main_loop_running
171
+ end
172
+ end
173
+
174
+ # Enter the request handler's main loop.
175
+ def main_loop
176
+ debug("Entering request handler main loop")
177
+ reset_signal_handlers
178
+ begin
179
+ @graceful_termination_pipe = IO.pipe
180
+ @graceful_termination_pipe[0].close_on_exec!
181
+ @graceful_termination_pipe[1].close_on_exec!
182
+
183
+ @main_loop_thread_lock.synchronize do
184
+ @main_loop_generation += 1
185
+ @main_loop_running = true
186
+ @main_loop_thread_cond.broadcast
187
+
188
+ @select_timeout = nil
189
+
190
+ @selectable_sockets = []
191
+ @server_sockets.each_value do |value|
192
+ socket = value[2]
193
+ @selectable_sockets << socket if socket
194
+ end
195
+ @selectable_sockets << @owner_pipe
196
+ @selectable_sockets << @graceful_termination_pipe[0]
197
+ end
198
+
199
+ install_useful_signal_handlers
200
+ start_threads
201
+ wait_until_termination_requested
202
+ wait_until_all_threads_are_idle
203
+ terminate_threads
204
+ debug("Request handler main loop exited normally")
205
+
206
+ rescue EOFError
207
+ # Exit main loop.
208
+ trace(2, "Request handler main loop interrupted by EOFError exception")
209
+ rescue Interrupt
210
+ # Exit main loop.
211
+ trace(2, "Request handler main loop interrupted by Interrupt exception")
212
+ rescue SignalException => signal
213
+ trace(2, "Request handler main loop interrupted by SignalException")
214
+ if signal.message != HARD_TERMINATION_SIGNAL
215
+ raise
216
+ end
217
+ rescue Exception => e
218
+ trace(2, "Request handler main loop interrupted by #{e.class} exception")
219
+ raise
220
+ ensure
221
+ debug("Exiting request handler main loop")
222
+ revert_signal_handlers
223
+ @main_loop_thread_lock.synchronize do
224
+ @graceful_termination_pipe[1].close rescue nil
225
+ @graceful_termination_pipe[0].close rescue nil
226
+ @selectable_sockets = []
227
+ @main_loop_generation += 1
228
+ @main_loop_running = false
229
+ @main_loop_thread_cond.broadcast
230
+ end
231
+ end
232
+ end
233
+
234
+ # Start the main loop in a new thread. This thread will be stopped by #cleanup.
235
+ def start_main_loop_thread
236
+ current_generation = @main_loop_generation
237
+ @main_loop_thread = create_thread_and_abort_on_exception do
238
+ main_loop
239
+ end
240
+ @main_loop_thread_lock.synchronize do
241
+ while @main_loop_generation == current_generation
242
+ @main_loop_thread_cond.wait(@main_loop_thread_lock)
243
+ end
244
+ end
245
+ end
246
+
247
+ private
248
+ def should_use_unix_sockets?
249
+ # Historical note:
250
+ # There seems to be a bug in MacOS X Leopard w.r.t. Unix server
251
+ # sockets file descriptors that are passed to another process.
252
+ # Usually Unix server sockets work fine, but when they're passed
253
+ # to another process, then clients that connect to the socket
254
+ # can incorrectly determine that the client socket is closed,
255
+ # even though that's not actually the case. More specifically:
256
+ # recv()/read() calls on these client sockets can return 0 even
257
+ # when we know EOF is not reached.
258
+ #
259
+ # The ApplicationPool infrastructure used to connect to a backend
260
+ # process's Unix socket in the helper server process, and then
261
+ # pass the connection file descriptor to the web server, which
262
+ # triggers this kernel bug. We used to work around this by using
263
+ # TCP sockets instead of Unix sockets; TCP sockets can still fail
264
+ # with this fake-EOF bug once in a while, but not nearly as often
265
+ # as with Unix sockets.
266
+ #
267
+ # This problem no longer applies today. The web server now passes
268
+ # all I/O through the HelperAgent, and the bug is no longer
269
+ # triggered. Nevertheless, we keep this function intact so that
270
+ # if something like this ever happens again, we know why, and we
271
+ # can easily reactivate the workaround. Or maybe if we just need
272
+ # TCP sockets for some other reason.
273
+
274
+ #return RUBY_PLATFORM !~ /darwin/
275
+
276
+ ruby_engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby"
277
+ # Unix domain socket implementation on JRuby
278
+ # is still bugged as of version 1.7.0. They can
279
+ # cause unexplicable freezes when used in combination
280
+ # with threading.
281
+ return !@force_http_session && ruby_engine != "jruby"
282
+ end
283
+
284
+ def create_unix_socket_on_filesystem(options)
285
+ if defined?(NativeSupport)
286
+ unix_path_max = NativeSupport::UNIX_PATH_MAX
287
+ else
288
+ unix_path_max = options.fetch('UNIX_PATH_MAX', 100).to_i
289
+ end
290
+ if options['socket_dir']
291
+ socket_dir = options['socket_dir']
292
+ socket_prefix = "ruby"
293
+ else
294
+ socket_dir = Dir.tmpdir
295
+ socket_prefix = "PsgRubyApp"
296
+ end
297
+
298
+ retry_at_most(128, Errno::EADDRINUSE) do
299
+ socket_address = "#{socket_dir}/#{socket_prefix}.#{generate_random_id(:base64)}"
300
+ socket_address = socket_address.slice(0, unix_path_max - 10)
301
+ socket = UNIXServer.new(socket_address)
302
+ socket.listen(BACKLOG_SIZE)
303
+ socket.binmode
304
+ socket.sync = true
305
+ socket.close_on_exec!
306
+ File.chmod(0600, socket_address)
307
+ ["unix:#{socket_address}", socket]
308
+ end
309
+ end
310
+
311
+ def create_tcp_socket
312
+ # We use "127.0.0.1" as address in order to force
313
+ # TCPv4 instead of TCPv6.
314
+ socket = TCPServer.new('127.0.0.1', 0)
315
+ socket.listen(BACKLOG_SIZE)
316
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
317
+ socket.binmode
318
+ socket.sync = true
319
+ socket.close_on_exec!
320
+ socket_address = "tcp://127.0.0.1:#{socket.addr[1]}"
321
+ return [socket_address, socket]
322
+ end
323
+
324
+ # Reset signal handlers to their default handler, and install some
325
+ # special handlers for a few signals. The previous signal handlers
326
+ # will be put back by calling revert_signal_handlers.
327
+ def reset_signal_handlers
328
+ Signal.list_trappable.each_key do |signal|
329
+ begin
330
+ prev_handler = trap(signal, DEFAULT)
331
+ if prev_handler != DEFAULT
332
+ @previous_signal_handlers[signal] = prev_handler
333
+ end
334
+ rescue ArgumentError
335
+ # Signal cannot be trapped; ignore it.
336
+ end
337
+ end
338
+ trap('HUP', IGNORE)
339
+ PhusionPassenger.call_event(:after_installing_signal_handlers)
340
+ end
341
+
342
+ def install_useful_signal_handlers
343
+ trappable_signals = Signal.list_trappable
344
+
345
+ trap('ABRT') do
346
+ print_status_report
347
+ end if trappable_signals.has_key?('ABRT')
348
+ trap('QUIT') do
349
+ print_status_report
350
+ end if trappable_signals.has_key?('QUIT')
351
+ end
352
+
353
+ def revert_signal_handlers
354
+ @previous_signal_handlers.each_pair do |signal, handler|
355
+ trap(signal, handler)
356
+ end
357
+ end
358
+
359
+ def print_status_report
360
+ warn(Utils.global_backtrace_report)
361
+ warn("Threads: #{@threads.inspect}")
362
+ end
363
+
364
+ def start_threads
365
+ common_options = {
366
+ :app => @app,
367
+ :app_group_name => @app_group_name,
368
+ :connect_password => @connect_password,
369
+ :union_station_core => @union_station_core,
370
+ :keepalive_enabled => @keepalive
371
+ }
372
+ main_socket_options = common_options.merge(
373
+ :server_socket => @main_socket,
374
+ :socket_name => "main socket",
375
+ :protocol => @server_sockets[:main][:protocol] == :session ?
376
+ :session :
377
+ :http
378
+ )
379
+ http_socket_options = common_options.merge(
380
+ :server_socket => @http_socket,
381
+ :socket_name => "HTTP socket",
382
+ :protocol => :http
383
+ )
384
+
385
+ # Used for marking threads that have finished initializing,
386
+ # or failed during initialization. Threads that are not yet done
387
+ # are not in `initialization_state`. Threads that have succeeded
388
+ # set their own state to true. Threads that have failed set their
389
+ # own state to false.
390
+ initialization_state_mutex = Mutex.new
391
+ initialization_state_cond = ConditionVariable.new
392
+ initialization_state = {}
393
+ set_initialization_state = lambda do |value|
394
+ initialization_state_mutex.synchronize do
395
+ initialization_state[Thread.current] = value
396
+ initialization_state_cond.signal
397
+ end
398
+ end
399
+ set_initialization_state_to_true = lambda do
400
+ set_initialization_state.call(true)
401
+ end
402
+
403
+ # Actually start all the threads.
404
+ thread_handler = @thread_handler
405
+ expected_nthreads = 0
406
+
407
+ @threads_mutex.synchronize do
408
+ @concurrency.times do |i|
409
+ thread = create_thread_and_abort_on_exception(i) do |number|
410
+ begin
411
+ Thread.current[:name] = "Worker #{number + 1}"
412
+ handler = thread_handler.new(self, main_socket_options)
413
+ handler.install
414
+ handler.main_loop(set_initialization_state_to_true)
415
+ ensure
416
+ set_initialization_state.call(false)
417
+ unregister_current_thread
418
+ end
419
+ end
420
+ @threads << thread
421
+ expected_nthreads += 1
422
+ end
423
+
424
+ thread = create_thread_and_abort_on_exception do
425
+ begin
426
+ Thread.current[:name] = "HTTP helper worker"
427
+ handler = thread_handler.new(self, http_socket_options)
428
+ handler.install
429
+ handler.main_loop(set_initialization_state_to_true)
430
+ ensure
431
+ set_initialization_state.call(false)
432
+ unregister_current_thread
433
+ end
434
+ end
435
+ @threads << thread
436
+ expected_nthreads += 1
437
+ end
438
+
439
+ # Wait until all threads have finished starting.
440
+ initialization_state_mutex.synchronize do
441
+ while initialization_state.size != expected_nthreads
442
+ initialization_state_cond.wait(initialization_state_mutex)
443
+ end
444
+ end
445
+ end
446
+
447
+ def unregister_current_thread
448
+ @threads_mutex.synchronize do
449
+ @threads.delete(Thread.current)
450
+ end
451
+ end
452
+
453
+ def wait_until_termination_requested
454
+ ruby_engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby"
455
+ if ruby_engine == "jruby"
456
+ # On JRuby, selecting on an input TTY always returns, so
457
+ # we use threads to do the job.
458
+ owner_pipe_watcher = IO.pipe
459
+ owner_pipe_watcher_thread = create_thread_and_abort_on_exception do
460
+ Thread.current[:name] = "Owner pipe waiter"
461
+ begin
462
+ @owner_pipe.read(1)
463
+ ensure
464
+ owner_pipe_watcher[1].write('x')
465
+ end
466
+ end
467
+ begin
468
+ ios = select([owner_pipe_watcher[0], @graceful_termination_pipe[0]])[0]
469
+ if ios.include?(owner_pipe_watcher[0])
470
+ trace(2, "Owner pipe closed")
471
+ else
472
+ trace(2, "Graceful termination pipe closed")
473
+ end
474
+ ensure
475
+ owner_pipe_watcher_thread.kill
476
+ owner_pipe_watcher_thread.join
477
+ owner_pipe_watcher[0].close if !owner_pipe_watcher[0].closed?
478
+ owner_pipe_watcher[1].close if !owner_pipe_watcher[1].closed?
479
+ end
480
+ else
481
+ ios = select([@owner_pipe, @graceful_termination_pipe[0]])[0]
482
+ if ios.include?(@owner_pipe)
483
+ trace(2, "Owner pipe closed")
484
+ else
485
+ trace(2, "Graceful termination pipe closed")
486
+ end
487
+ end
488
+ end
489
+
490
+ def wakeup_all_threads
491
+ threads = []
492
+ if get_socket_address_type(@server_sockets[:main][:address]) == :unix &&
493
+ !File.exist?(@server_sockets[:main][:address].sub(/^unix:/, ''))
494
+ # It looks like someone deleted the Unix domain socket we listen on.
495
+ # This makes it impossible to wake up the worker threads gracefully,
496
+ # so we hard kill them.
497
+ warn("Unix domain socket gone; force aborting all threads")
498
+ @threads_mutex.synchronize do
499
+ @threads.each do |thread|
500
+ thread.raise(RuntimeError.new("Force abort"))
501
+ end
502
+ end
503
+ else
504
+ @concurrency.times do
505
+ threads << create_thread_and_abort_on_exception(@server_sockets[:main][:address]) do |address|
506
+ begin
507
+ debug("Shutting down worker thread by connecting to #{address}")
508
+ connect_to_server(address).close
509
+ rescue Errno::ECONNREFUSED
510
+ debug("Worker thread listening on #{address} already exited")
511
+ rescue SystemCallError, IOError => e
512
+ debug("Error shutting down worker thread (#{address}): #{e} (#{e.class})")
513
+ end
514
+ end
515
+ end
516
+ end
517
+ threads << create_thread_and_abort_on_exception(@server_sockets[:http][:address]) do |address|
518
+ begin
519
+ debug("Shutting down HTTP thread by connecting to #{address}")
520
+ connect_to_server(address).close
521
+ rescue Errno::ECONNREFUSED
522
+ debug("Worker thread listening on #{address} already exited")
523
+ rescue SystemCallError, IOError => e
524
+ debug("Error shutting down HTTP thread (#{address}): #{e} (#{e.class})")
525
+ end
526
+ end
527
+ return threads
528
+ end
529
+
530
+ def terminate_threads
531
+ debug("Stopping all threads")
532
+ threads = @threads_mutex.synchronize do
533
+ @threads.dup
534
+ end
535
+ threads.each do |thr|
536
+ thr.raise(ThreadHandler::Interrupted.new)
537
+ end
538
+ threads.each do |thr|
539
+ thr.join
540
+ end
541
+ debug("All threads stopped")
542
+ end
543
+
544
+ def wait_until_all_threads_are_idle
545
+ debug("Waiting until all threads have become idle...")
546
+
547
+ # We wait until 100 ms have passed since all handlers have become
548
+ # interruptable and remained in the same iterations.
549
+
550
+ done = false
551
+
552
+ while !done
553
+ handlers = @threads_mutex.synchronize do
554
+ @threads.map do |thr|
555
+ thr[:passenger_thread_handler]
556
+ end
557
+ end
558
+ debug("There are currently #{handlers.size} threads")
559
+ if handlers.empty?
560
+ # There are no threads, so we're done.
561
+ done = true
562
+ break
563
+ end
564
+
565
+ # Record initial state.
566
+ handlers.each { |h| h.stats_mutex.lock }
567
+ iterations = handlers.map { |h| h.iteration }
568
+ handlers.each { |h| h.stats_mutex.unlock }
569
+
570
+ start_time = Time.now
571
+ sleep 0.01
572
+
573
+ while true
574
+ if handlers.size != @threads_mutex.synchronize { @threads.size }
575
+ debug("The number of threads changed. Restarting waiting algorithm")
576
+ break
577
+ end
578
+
579
+ # Record current state.
580
+ handlers.each { |h| h.stats_mutex.lock }
581
+ all_interruptable = handlers.all? { |h| h.interruptable }
582
+ new_iterations = handlers.map { |h| h.iteration }
583
+
584
+ # Are all threads interruptable and has there been no activity
585
+ # since last time we checked?
586
+ if all_interruptable && new_iterations == iterations
587
+ # Yes. If enough time has passed then we're done.
588
+ handlers.each { |h| h.stats_mutex.unlock }
589
+ if Time.now >= start_time + 0.1
590
+ done = true
591
+ break
592
+ end
593
+ else
594
+ # No. We reset the timer and check again later.
595
+ handlers.each { |h| h.stats_mutex.unlock }
596
+ iterations = new_iterations
597
+ start_time = Time.now
598
+ sleep 0.01
599
+ end
600
+ end
601
+ end
602
+
603
+ debug("All threads are now idle")
604
+ end
605
+ end
616
606
 
617
607
  end # module PhusionPassenger