passenger 2.2.2 → 2.2.3

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 (254) hide show
  1. data/DEVELOPERS.TXT +13 -3
  2. data/Rakefile +42 -33
  3. data/bin/passenger-install-apache2-module +1 -2
  4. data/bin/passenger-install-nginx-module +7 -19
  5. data/bin/passenger-status +64 -15
  6. data/bin/passenger-stress-test +2 -2
  7. data/doc/ApplicationPool algorithm.txt +26 -22
  8. data/doc/Users guide Apache.html +374 -149
  9. data/doc/Users guide Apache.txt +318 -51
  10. data/doc/Users guide Nginx.html +13 -13
  11. data/doc/Users guide Nginx.txt +7 -2
  12. data/doc/cxxapi/Bucket_8h-source.html +62 -25
  13. data/doc/cxxapi/Configuration_8h-source.html +343 -326
  14. data/doc/cxxapi/DirectoryMapper_8h-source.html +12 -12
  15. data/doc/cxxapi/Hooks_8h-source.html +1 -1
  16. data/doc/cxxapi/annotated.html +1 -1
  17. data/doc/cxxapi/classHooks-members.html +1 -1
  18. data/doc/cxxapi/classHooks.html +1 -1
  19. data/doc/cxxapi/classPassenger_1_1DirectoryMapper-members.html +2 -2
  20. data/doc/cxxapi/classPassenger_1_1DirectoryMapper.html +9 -9
  21. data/doc/cxxapi/classes.html +1 -1
  22. data/doc/cxxapi/definitions_8h-source.html +1 -1
  23. data/doc/cxxapi/files.html +1 -1
  24. data/doc/cxxapi/functions.html +2 -2
  25. data/doc/cxxapi/functions_func.html +2 -2
  26. data/doc/cxxapi/graph_legend.html +1 -1
  27. data/doc/cxxapi/group__Configuration.html +1 -1
  28. data/doc/cxxapi/group__Core.html +1 -1
  29. data/doc/cxxapi/group__Hooks.html +1 -1
  30. data/doc/cxxapi/group__Support.html +1 -1
  31. data/doc/cxxapi/main.html +1 -1
  32. data/doc/cxxapi/modules.html +1 -1
  33. data/doc/rdoc/classes/ConditionVariable.html +194 -0
  34. data/doc/rdoc/classes/Exception.html +120 -0
  35. data/doc/rdoc/classes/GC.html +113 -0
  36. data/doc/rdoc/classes/IO.html +169 -0
  37. data/doc/rdoc/classes/PhusionPassenger.html +238 -0
  38. data/doc/rdoc/classes/PhusionPassenger/AbstractInstaller.html +153 -0
  39. data/doc/rdoc/classes/PhusionPassenger/AbstractRequestHandler.html +517 -0
  40. data/doc/rdoc/classes/PhusionPassenger/AbstractServer.html +719 -0
  41. data/doc/rdoc/classes/PhusionPassenger/AbstractServer/ServerAlreadyStarted.html +97 -0
  42. data/doc/rdoc/classes/PhusionPassenger/AbstractServer/ServerError.html +96 -0
  43. data/doc/rdoc/classes/PhusionPassenger/AbstractServer/ServerNotStarted.html +97 -0
  44. data/doc/rdoc/classes/PhusionPassenger/AbstractServer/UnknownMessage.html +96 -0
  45. data/doc/rdoc/classes/PhusionPassenger/AbstractServerCollection.html +598 -0
  46. data/doc/rdoc/classes/PhusionPassenger/AdminTools.html +140 -0
  47. data/doc/rdoc/classes/PhusionPassenger/AdminTools/ControlProcess.html +317 -0
  48. data/doc/rdoc/classes/PhusionPassenger/AdminTools/ControlProcess/Instance.html +138 -0
  49. data/doc/rdoc/classes/PhusionPassenger/AppInitError.html +154 -0
  50. data/doc/rdoc/classes/PhusionPassenger/Application.html +283 -0
  51. data/doc/rdoc/classes/PhusionPassenger/ConsoleTextTemplate.html +172 -0
  52. data/doc/rdoc/classes/PhusionPassenger/FrameworkInitError.html +145 -0
  53. data/doc/rdoc/classes/PhusionPassenger/HTMLTemplate.html +175 -0
  54. data/doc/rdoc/classes/PhusionPassenger/InitializationError.html +141 -0
  55. data/doc/rdoc/classes/PhusionPassenger/InvalidPath.html +92 -0
  56. data/doc/rdoc/classes/PhusionPassenger/MessageChannel.html +489 -0
  57. data/doc/rdoc/classes/PhusionPassenger/NativeSupport.html +350 -0
  58. data/doc/rdoc/classes/PhusionPassenger/Rack.html +91 -0
  59. data/doc/rdoc/classes/PhusionPassenger/Rack/ApplicationSpawner.html +188 -0
  60. data/doc/rdoc/classes/PhusionPassenger/Rack/RequestHandler.html +194 -0
  61. data/doc/rdoc/classes/PhusionPassenger/Railz.html +95 -0
  62. data/doc/rdoc/classes/PhusionPassenger/Railz/ApplicationSpawner.html +442 -0
  63. data/doc/rdoc/classes/PhusionPassenger/Railz/ApplicationSpawner/Error.html +98 -0
  64. data/doc/rdoc/classes/PhusionPassenger/Railz/CGIFixed.html +200 -0
  65. data/doc/rdoc/classes/PhusionPassenger/Railz/FrameworkSpawner.html +436 -0
  66. data/doc/rdoc/classes/PhusionPassenger/Railz/FrameworkSpawner/Error.html +98 -0
  67. data/doc/rdoc/classes/PhusionPassenger/Railz/RequestHandler.html +155 -0
  68. data/doc/rdoc/classes/PhusionPassenger/SpawnManager.html +402 -0
  69. data/doc/rdoc/classes/PhusionPassenger/UnknownError.html +125 -0
  70. data/doc/rdoc/classes/PhusionPassenger/Utils.html +805 -0
  71. data/doc/rdoc/classes/PhusionPassenger/VersionNotFound.html +140 -0
  72. data/doc/rdoc/classes/PhusionPassenger/WSGI.html +89 -0
  73. data/doc/rdoc/classes/PhusionPassenger/WSGI/ApplicationSpawner.html +188 -0
  74. data/doc/rdoc/classes/PlatformInfo.html +831 -0
  75. data/doc/rdoc/classes/RakeExtensions.html +197 -0
  76. data/doc/rdoc/classes/Signal.html +131 -0
  77. data/doc/rdoc/created.rid +1 -0
  78. data/doc/rdoc/files/DEVELOPERS_TXT.html +255 -0
  79. data/doc/rdoc/files/README.html +157 -0
  80. data/doc/rdoc/files/ext/phusion_passenger/native_support_c.html +92 -0
  81. data/doc/rdoc/files/lib/phusion_passenger/abstract_installer_rb.html +129 -0
  82. data/doc/rdoc/files/lib/phusion_passenger/abstract_request_handler_rb.html +131 -0
  83. data/doc/rdoc/files/lib/phusion_passenger/abstract_server_collection_rb.html +126 -0
  84. data/doc/rdoc/files/lib/phusion_passenger/abstract_server_rb.html +130 -0
  85. data/doc/rdoc/files/lib/phusion_passenger/admin_tools/control_process_rb.html +130 -0
  86. data/doc/rdoc/files/lib/phusion_passenger/admin_tools_rb.html +122 -0
  87. data/doc/rdoc/files/lib/phusion_passenger/application_rb.html +127 -0
  88. data/doc/rdoc/files/lib/phusion_passenger/console_text_template_rb.html +126 -0
  89. data/doc/rdoc/files/lib/phusion_passenger/constants_rb.html +122 -0
  90. data/doc/rdoc/files/lib/phusion_passenger/dependencies_rb.html +134 -0
  91. data/doc/rdoc/files/lib/phusion_passenger/events_rb.html +122 -0
  92. data/doc/rdoc/files/lib/phusion_passenger/exceptions_rb.html +122 -0
  93. data/doc/rdoc/files/lib/phusion_passenger/html_template_rb.html +126 -0
  94. data/doc/rdoc/files/lib/phusion_passenger/message_channel_rb.html +122 -0
  95. data/doc/rdoc/files/lib/phusion_passenger/packaging_rb.html +122 -0
  96. data/doc/rdoc/files/lib/phusion_passenger/platform_info_rb.html +127 -0
  97. data/doc/rdoc/files/lib/phusion_passenger/rack/application_spawner_rb.html +133 -0
  98. data/doc/rdoc/files/lib/phusion_passenger/rack/request_handler_rb.html +127 -0
  99. data/doc/rdoc/files/lib/phusion_passenger/railz/application_spawner_rb.html +143 -0
  100. data/doc/rdoc/files/lib/phusion_passenger/railz/cgi_fixed_rb.html +126 -0
  101. data/doc/rdoc/files/lib/phusion_passenger/railz/framework_spawner_rb.html +145 -0
  102. data/doc/rdoc/files/lib/phusion_passenger/railz/request_handler_rb.html +127 -0
  103. data/doc/rdoc/files/lib/phusion_passenger/simple_benchmarking_rb.html +122 -0
  104. data/doc/rdoc/files/lib/phusion_passenger/spawn_manager_rb.html +161 -0
  105. data/doc/rdoc/files/lib/phusion_passenger/utils_rb.html +175 -0
  106. data/doc/rdoc/files/lib/phusion_passenger/wsgi/application_spawner_rb.html +129 -0
  107. data/doc/rdoc/files/misc/rake/extensions_rb.html +130 -0
  108. data/doc/rdoc/fr_class_index.html +90 -0
  109. data/doc/rdoc/fr_file_index.html +76 -0
  110. data/doc/rdoc/fr_method_index.html +200 -0
  111. data/doc/rdoc/index.html +26 -0
  112. data/doc/rdoc/rdoc-style.css +187 -0
  113. data/doc/users_guide_snippets/rackup_specifications.txt +2 -8
  114. data/ext/apache2/Bucket.cpp +71 -38
  115. data/ext/apache2/Bucket.h +53 -16
  116. data/ext/apache2/Configuration.cpp +15 -0
  117. data/ext/apache2/Configuration.h +19 -2
  118. data/ext/apache2/DirectoryMapper.h +10 -10
  119. data/ext/apache2/Hooks.cpp +334 -74
  120. data/ext/boost/mpl/apply.hpp +5 -1
  121. data/ext/boost/mpl/apply_wrap.hpp +5 -2
  122. data/ext/boost/mpl/aux_/full_lambda.hpp +5 -1
  123. data/ext/boost/mpl/bind.hpp +5 -1
  124. data/ext/common/Application.h +11 -31
  125. data/ext/common/ApplicationPool.h +2 -1
  126. data/ext/common/ApplicationPoolServer.h +61 -20
  127. data/ext/common/ApplicationPoolServerExecutable.cpp +132 -4
  128. data/ext/common/ApplicationPoolStatusReporter.h +189 -65
  129. data/ext/common/Base64.cpp +143 -0
  130. data/ext/common/Base64.h +57 -0
  131. data/ext/common/CachedFileStat.cpp +25 -82
  132. data/ext/common/CachedFileStat.h +11 -125
  133. data/ext/common/CachedFileStat.hpp +243 -0
  134. data/ext/common/Exceptions.h +13 -0
  135. data/ext/common/FileChangeChecker.h +209 -0
  136. data/ext/common/Logging.h +3 -2
  137. data/ext/common/MessageChannel.h +10 -10
  138. data/ext/common/PoolOptions.h +72 -5
  139. data/ext/common/SpawnManager.h +11 -8
  140. data/ext/common/StandardApplicationPool.h +38 -39
  141. data/ext/common/StaticString.h +1 -0
  142. data/ext/common/StringListCreator.h +83 -0
  143. data/ext/common/SystemTime.h +3 -2
  144. data/ext/common/Timer.h +88 -0
  145. data/ext/common/Utils.cpp +161 -42
  146. data/ext/common/Utils.h +62 -31
  147. data/ext/common/Version.h +1 -1
  148. data/ext/nginx/Configuration.c +0 -4
  149. data/ext/nginx/ContentHandler.c +8 -6
  150. data/ext/nginx/HelperServer.cpp +45 -55
  151. data/ext/nginx/HttpStatusExtractor.h +4 -0
  152. data/ext/nginx/StaticContentHandler.c +25 -5
  153. data/ext/nginx/config +3 -0
  154. data/ext/nginx/ngx_http_passenger_module.c +72 -17
  155. data/ext/nginx/ngx_http_passenger_module.h +2 -2
  156. data/lib/phusion_passenger/abstract_request_handler.rb +15 -7
  157. data/lib/phusion_passenger/abstract_server.rb +16 -2
  158. data/lib/phusion_passenger/admin_tools/control_process.rb +36 -25
  159. data/lib/phusion_passenger/constants.rb +1 -1
  160. data/lib/phusion_passenger/dependencies.rb +10 -0
  161. data/lib/phusion_passenger/platform_info.rb +1 -1
  162. data/lib/phusion_passenger/rack/application_spawner.rb +21 -2
  163. data/lib/phusion_passenger/rack/request_handler.rb +10 -0
  164. data/lib/phusion_passenger/railz/application_spawner.rb +38 -2
  165. data/lib/phusion_passenger/railz/framework_spawner.rb +26 -28
  166. data/lib/phusion_passenger/railz/request_handler.rb +5 -1
  167. data/lib/phusion_passenger/spawn_manager.rb +6 -2
  168. data/lib/phusion_passenger/utils.rb +79 -27
  169. data/misc/rake/cplusplus.rb +5 -5
  170. data/test/ApplicationPoolServerTest.cpp +42 -0
  171. data/test/ApplicationPoolTest.cpp +255 -267
  172. data/test/Base64Test.cpp +48 -0
  173. data/test/CachedFileStatTest.cpp +243 -103
  174. data/test/FileChangeCheckerTest.cpp +331 -0
  175. data/test/PoolOptionsTest.cpp +80 -0
  176. data/test/UtilsTest.cpp +5 -17
  177. data/test/integration_tests/apache2_tests.rb +15 -4
  178. data/test/integration_tests/mycook_spec.rb +3 -4
  179. data/test/oxt/syscall_interruption_test.cpp +2 -14
  180. data/test/ruby/abstract_server_collection_spec.rb +1 -1
  181. data/test/ruby/abstract_server_spec.rb +35 -1
  182. data/test/ruby/rack/application_spawner_spec.rb +23 -6
  183. data/test/ruby/rails/application_spawner_spec.rb +6 -6
  184. data/test/ruby/rails/framework_spawner_spec.rb +6 -5
  185. data/test/ruby/rails/minimal_spawner_spec.rb +19 -0
  186. data/test/ruby/rails/spawner_error_handling_spec.rb +62 -7
  187. data/test/ruby/spawn_manager_spec.rb +10 -7
  188. data/test/ruby/spawn_server_spec.rb +1 -1
  189. data/test/ruby/utils_spec.rb +193 -20
  190. data/test/ruby/wsgi/application_spawner_spec.rb +3 -1
  191. data/test/stub/apache2/httpd.conf.erb +3 -0
  192. data/test/stub/rack/config.ru +1 -1
  193. data/test/stub/rails_apps/mycook/app/controllers/welcome_controller.rb +8 -0
  194. data/test/support/Support.cpp +84 -0
  195. data/test/support/Support.h +66 -8
  196. data/test/support/config.rb +14 -2
  197. data/test/support/test_helper.rb +5 -0
  198. data/vendor/rack-1.0.0-git/lib/rack/auth/openid.rb +123 -116
  199. data/vendor/rack-1.0.0-git/lib/rack/cascade.rb +17 -12
  200. data/vendor/rack-1.0.0-git/lib/rack/commonlogger.rb +34 -43
  201. data/vendor/rack-1.0.0-git/lib/rack/handler/cgi.rb +1 -1
  202. data/vendor/rack-1.0.0-git/lib/rack/handler/fastcgi.rb +1 -1
  203. data/vendor/rack-1.0.0-git/lib/rack/handler/lsws.rb +1 -1
  204. data/vendor/rack-1.0.0-git/lib/rack/handler/mongrel.rb +1 -1
  205. data/vendor/rack-1.0.0-git/lib/rack/handler/scgi.rb +1 -1
  206. data/vendor/rack-1.0.0-git/lib/rack/handler/webrick.rb +1 -1
  207. data/vendor/rack-1.0.0-git/lib/rack/mock.rb +4 -17
  208. data/vendor/rack-1.0.0-git/lib/rack/request.rb +3 -9
  209. data/vendor/rack-1.0.0-git/lib/rack/rewindable_input.rb +2 -0
  210. data/vendor/rack-1.0.0-git/lib/rack/utils.rb +38 -12
  211. metadata +231 -186
  212. data/ext/common/FileChecker.h +0 -112
  213. data/test/FileCheckerTest.cpp +0 -79
  214. data/test/stub/minimal-railsapp/README +0 -3
  215. data/test/stub/minimal-railsapp/config/application.rb +0 -0
  216. data/test/stub/minimal-railsapp/config/environment.rb +0 -3
  217. data/test/stub/minimal-railsapp/vendor/rails/actionmailer/lib/action_mailer.rb +0 -0
  218. data/test/stub/minimal-railsapp/vendor/rails/actionpack/lib/action_controller.rb +0 -10
  219. data/test/stub/minimal-railsapp/vendor/rails/actionpack/lib/action_pack.rb +0 -0
  220. data/test/stub/minimal-railsapp/vendor/rails/actionpack/lib/action_view.rb +0 -0
  221. data/test/stub/minimal-railsapp/vendor/rails/activerecord/lib/active_record.rb +0 -7
  222. data/test/stub/minimal-railsapp/vendor/rails/activeresource/lib/active_resource.rb +0 -0
  223. data/test/stub/minimal-railsapp/vendor/rails/activesupport/lib/active_support.rb +0 -17
  224. data/test/stub/minimal-railsapp/vendor/rails/activesupport/lib/active_support/whiny_nil.rb +0 -0
  225. data/test/stub/minimal-railsapp/vendor/rails/railties/lib/dispatcher.rb +0 -0
  226. data/test/stub/minimal-railsapp/vendor/rails/railties/lib/initializer.rb +0 -8
  227. data/test/stub/minimal-railsapp/vendor/rails/railties/lib/ruby_version_check.rb +0 -1
  228. data/test/stub/railsapp/app/controllers/application.rb +0 -12
  229. data/test/stub/railsapp/app/controllers/bar_controller.rb +0 -5
  230. data/test/stub/railsapp/app/controllers/bar_controller_1.txt +0 -5
  231. data/test/stub/railsapp/app/controllers/bar_controller_2.txt +0 -5
  232. data/test/stub/railsapp/app/controllers/foo_controller.rb +0 -9
  233. data/test/stub/railsapp/app/helpers/application_helper.rb +0 -3
  234. data/test/stub/railsapp/config/boot.rb +0 -108
  235. data/test/stub/railsapp/config/database.yml +0 -19
  236. data/test/stub/railsapp/config/environment.rb +0 -59
  237. data/test/stub/railsapp/config/environments/development.rb +0 -18
  238. data/test/stub/railsapp/config/environments/production.rb +0 -19
  239. data/test/stub/railsapp/config/initializers/inflections.rb +0 -10
  240. data/test/stub/railsapp/config/initializers/mime_types.rb +0 -5
  241. data/test/stub/railsapp/config/routes.rb +0 -35
  242. data/test/stub/railsapp/public/useless.txt +0 -1
  243. data/test/stub/railsapp2/app/controllers/application.rb +0 -12
  244. data/test/stub/railsapp2/app/controllers/foo_controller.rb +0 -5
  245. data/test/stub/railsapp2/app/helpers/application_helper.rb +0 -3
  246. data/test/stub/railsapp2/config/boot.rb +0 -108
  247. data/test/stub/railsapp2/config/database.yml +0 -19
  248. data/test/stub/railsapp2/config/environment.rb +0 -59
  249. data/test/stub/railsapp2/config/environments/development.rb +0 -18
  250. data/test/stub/railsapp2/config/environments/production.rb +0 -19
  251. data/test/stub/railsapp2/config/initializers/inflections.rb +0 -10
  252. data/test/stub/railsapp2/config/initializers/mime_types.rb +0 -5
  253. data/test/stub/railsapp2/config/routes.rb +0 -35
  254. data/test/stub/railsapp2/public/useless.txt +0 -1
@@ -28,7 +28,10 @@ module Railz
28
28
 
29
29
  # A request handler for Ruby on Rails applications.
30
30
  class RequestHandler < AbstractRequestHandler
31
- NINJA_PATCHING_LOCK = Mutex.new
31
+ CONTENT_LENGTH = 'CONTENT_LENGTH' # :nodoc:
32
+ HTTP_CONTENT_LENGTH = 'HTTP_CONTENT_LENGTH' # :nodoc:
33
+
34
+ NINJA_PATCHING_LOCK = Mutex.new # :nodoc:
32
35
  @@ninja_patched_action_controller = false
33
36
 
34
37
  def initialize(owner_pipe, options = {})
@@ -41,6 +44,7 @@ class RequestHandler < AbstractRequestHandler
41
44
  protected
42
45
  # Overrided method.
43
46
  def process_request(headers, input, output)
47
+ headers[CONTENT_LENGTH] = headers[HTTP_CONTENT_LENGTH]
44
48
  cgi = CGIFixed.new(headers, input, output)
45
49
  ::Dispatcher.dispatch(cgi,
46
50
  ::ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS,
@@ -97,7 +97,7 @@ class SpawnManager < AbstractServer
97
97
  #
98
98
  # Other options are:
99
99
  #
100
- # ['lower_privilege', 'lowest_user' and 'environment']
100
+ # ['lower_privilege', 'lowest_user', 'environment', 'environment_variables', 'base_uri' and 'print_exceptions']
101
101
  # See Railz::ApplicationSpawner.new for an explanation of these options.
102
102
  #
103
103
  # ['app_type']
@@ -234,7 +234,11 @@ private
234
234
  else
235
235
  key = "version:#{framework_version}"
236
236
  create_spawner = proc do
237
- Railz::FrameworkSpawner.new(:version => framework_version)
237
+ framework_options = { :version => framework_version }
238
+ if options.has_key?(:print_framework_loading_exceptions)
239
+ framework_options[:print_framework_loading_exceptions] = options[:print_framework_loading_exceptions]
240
+ end
241
+ Railz::FrameworkSpawner.new(framework_options)
238
242
  end
239
243
  spawner_timeout = options["framework_spawner_timeout"]
240
244
  end
@@ -154,10 +154,10 @@ protected
154
154
  #
155
155
  # +current_location+ is a string which describes where the code is
156
156
  # currently at. Usually the current class name will be enough.
157
- def print_exception(current_location, exception)
157
+ def print_exception(current_location, exception, destination = STDERR)
158
158
  if !exception.is_a?(SystemExit)
159
- STDERR.puts(exception.backtrace_string(current_location))
160
- STDERR.flush
159
+ destination.puts(exception.backtrace_string(current_location))
160
+ destination.flush if destination.respond_to?(:flush)
161
161
  end
162
162
  end
163
163
 
@@ -178,9 +178,11 @@ protected
178
178
  if double_fork
179
179
  pid2 = fork
180
180
  if pid2.nil?
181
+ srand
181
182
  yield
182
183
  end
183
184
  else
185
+ srand
184
186
  yield
185
187
  end
186
188
  rescue Exception => e
@@ -201,9 +203,21 @@ protected
201
203
  # Run the given block. A message will be sent through +channel+ (a
202
204
  # MessageChannel object), telling the remote side whether the block
203
205
  # raised an exception, called exit(), or succeeded.
204
- # Returns whether the block succeeded.
205
- # Exceptions are not propagated, except for SystemExit.
206
- def report_app_init_status(channel)
206
+ #
207
+ # Anything written to $stderr and STDERR during execution of the block
208
+ # will be buffered. If <tt>write_stderr_contents_to</tt> is non-nil,
209
+ # then the buffered stderr data will be written to this object. In this
210
+ # case, <tt>write_stderr_contents_to</tt> must be an IO-like object.
211
+ # If <tt>write_stderr_contents_to</tt> is nil, then the stder data will
212
+ # be discarded.
213
+ #
214
+ # Returns whether the block succeeded, i.e. whether it didn't raise an
215
+ # exception.
216
+ #
217
+ # Exceptions are not propagated, except SystemExit and a few
218
+ # non-StandardExeption classes such as SignalException. Of the
219
+ # exceptions that are propagated, only SystemExit will be reported.
220
+ def report_app_init_status(channel, write_stderr_contents_to = STDERR)
207
221
  begin
208
222
  old_global_stderr = $stderr
209
223
  old_stderr = STDERR
@@ -218,10 +232,13 @@ protected
218
232
  Object.send(:remove_const, 'STDERR') rescue nil
219
233
  Object.const_set('STDERR', old_stderr)
220
234
  $stderr = old_global_stderr
221
- if tempfile
222
- tempfile.rewind
223
- stderr_output = tempfile.read
224
- tempfile.close rescue nil
235
+ tempfile.rewind
236
+ stderr_output = tempfile.read
237
+ tempfile.close rescue nil
238
+
239
+ if write_stderr_contents_to
240
+ write_stderr_contents_to.write(stderr_output)
241
+ write_stderr_contents_to.flush
225
242
  end
226
243
  end
227
244
  channel.write('success')
@@ -247,10 +264,24 @@ protected
247
264
  # received information, then an appropriate exception will be
248
265
  # raised.
249
266
  #
267
+ # If <tt>print_exception</tt> evaluates to true, then the
268
+ # exception message and the backtrace will also be printed.
269
+ # Where it is printed to depends on the type of
270
+ # <tt>print_exception</tt>:
271
+ # - If it responds to #puts, then the exception information will
272
+ # be printed using this method.
273
+ # - If it responds to #to_str, then the exception information
274
+ # will be appended to the file whose filename equals the return
275
+ # value of the #to_str call.
276
+ # - Otherwise, it will be printed to STDERR.
277
+ #
250
278
  # Raises:
251
- # - AppInitError
252
- # - IOError, SystemCallError, SocketError
253
- def unmarshal_and_raise_errors(channel, app_type = "rails")
279
+ # - AppInitError: this class wraps the exception information
280
+ # received through the channel.
281
+ # - IOError, SystemCallError, SocketError: these errors are
282
+ # raised if an error occurred while receiving the information
283
+ # through the channel.
284
+ def unmarshal_and_raise_errors(channel, print_exception = nil, app_type = "rails")
254
285
  args = channel.read
255
286
  if args.nil?
256
287
  raise EOFError, "Unexpected end-of-file detected."
@@ -259,8 +290,7 @@ protected
259
290
  if status == 'exception'
260
291
  child_exception = unmarshal_exception(channel.read_scalar)
261
292
  stderr = channel.read_scalar
262
- #print_exception(self.class.to_s, child_exception)
263
- raise AppInitError.new(
293
+ exception = AppInitError.new(
264
294
  "Application '#{@app_root}' raised an exception: " <<
265
295
  "#{child_exception.class} (#{child_exception.message})",
266
296
  child_exception,
@@ -269,9 +299,25 @@ protected
269
299
  elsif status == 'exit'
270
300
  child_exception = unmarshal_exception(channel.read_scalar)
271
301
  stderr = channel.read_scalar
272
- raise AppInitError.new("Application '#{@app_root}' exited during startup",
302
+ exception = AppInitError.new("Application '#{@app_root}' exited during startup",
273
303
  child_exception, app_type, stderr.empty? ? nil : stderr)
304
+ else
305
+ exception = nil
306
+ end
307
+
308
+ if print_exception && exception
309
+ if print_exception.respond_to?(:puts)
310
+ print_exception(self.class.to_s, child_exception, print_exception)
311
+ elsif print_exception.respond_to?(:to_str)
312
+ filename = print_exception.to_str
313
+ File.open(filename, 'a') do |f|
314
+ print_exception(self.class.to_s, child_exception, f)
315
+ end
316
+ else
317
+ print_exception(self.class.to_s, child_exception)
318
+ end
274
319
  end
320
+ raise exception if exception
275
321
  end
276
322
 
277
323
  # Lower the current process's privilege to the owner of the given file.
@@ -320,6 +366,10 @@ protected
320
366
  end
321
367
  end
322
368
 
369
+ def to_boolean(value)
370
+ return !(value.nil? || value == false || value == "false")
371
+ end
372
+
323
373
  def sanitize_spawn_options(options)
324
374
  defaults = {
325
375
  "lower_privilege" => true,
@@ -328,12 +378,15 @@ protected
328
378
  "app_type" => "rails",
329
379
  "spawn_method" => "smart-lv2",
330
380
  "framework_spawner_timeout" => -1,
331
- "app_spawner_timeout" => -1
381
+ "app_spawner_timeout" => -1,
382
+ "print_exceptions" => true
332
383
  }
333
384
  options = defaults.merge(options)
334
- options["lower_privilege"] = options["lower_privilege"].to_s == "true"
385
+ options["lower_privilege"] = to_boolean(options["lower_privilege"])
335
386
  options["framework_spawner_timeout"] = options["framework_spawner_timeout"].to_i
336
387
  options["app_spawner_timeout"] = options["app_spawner_timeout"].to_i
388
+ # Force this to be a boolean for easy use with Utils#unmarshal_and_raise_errors.
389
+ options["print_exceptions"] = to_boolean(options["print_exceptions"])
337
390
  return options
338
391
  end
339
392
 
@@ -367,6 +420,8 @@ protected
367
420
  def self.passenger_tmpdir=(dir)
368
421
  @@passenger_tmpdir = dir
369
422
  end
423
+
424
+ ####################################
370
425
  end
371
426
 
372
427
  end # module PhusionPassenger
@@ -479,16 +534,13 @@ end
479
534
  module Signal
480
535
  # Like Signal.list, but only returns signals that we can actually trap.
481
536
  def self.list_trappable
482
- ruby_engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : "mri"
537
+ ruby_engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby"
483
538
  case ruby_engine
484
- when "mri"
485
- if RUBY_VERSION >= '1.9.0'
486
- return Signal.list
487
- else
488
- result = Signal.list
489
- result.delete("ALRM")
490
- return result
491
- end
539
+ when "ruby"
540
+ result = Signal.list
541
+ result.delete("ALRM")
542
+ result.delete("VTALRM")
543
+ return result
492
544
  when "jruby"
493
545
  result = Signal.list
494
546
  result.delete("QUIT")
@@ -23,11 +23,11 @@
23
23
 
24
24
  # Rake functions for compiling/linking C++ stuff.
25
25
 
26
- def compile_c(source, flags = CXXFLAGS)
27
- sh "#{CXX} #{flags} -c #{source}"
26
+ def compile_c(source, flags = "#{PlatformInfo.portability_cflags} #{EXTRA_CXXFLAGS}")
27
+ sh "#{CC} #{flags} -c #{source}"
28
28
  end
29
29
 
30
- def compile_cxx(source, flags = CXXFLAGS)
30
+ def compile_cxx(source, flags = "#{PlatformInfo.portability_cflags} #{EXTRA_CXXFLAGS}")
31
31
  sh "#{CXX} #{flags} -c #{source}"
32
32
  end
33
33
 
@@ -43,11 +43,11 @@ def create_static_library(target, sources)
43
43
  sh "ranlib #{target}"
44
44
  end
45
45
 
46
- def create_executable(target, sources, linkflags = LDFLAGS)
46
+ def create_executable(target, sources, linkflags = "#{PlatformInfo.portability_cflags} #{EXTRA_CXXFLAGS} #{PlatformInfo.portability_ldflags} #{EXTRA_LDFLAGS}")
47
47
  sh "#{CXX} #{sources} -o #{target} #{linkflags}"
48
48
  end
49
49
 
50
- def create_shared_library(target, sources, flags = LDFLAGS)
50
+ def create_shared_library(target, sources, flags = "#{PlatformInfo.portability_cflags} #{EXTRA_CXXFLAGS} #{PlatformInfo.portability_ldflags} #{EXTRA_LDFLAGS}")
51
51
  if RUBY_PLATFORM =~ /darwin/
52
52
  shlib_flag = "-flat_namespace -bundle -undefined dynamic_lookup"
53
53
  else
@@ -68,5 +68,47 @@ namespace tut {
68
68
  }
69
69
  }
70
70
  }
71
+
72
+ /* A StringListCreator which not only returns a dummy value, but also
73
+ * increments a counter each time getItems() is called. */
74
+ class DummyStringListCreator: public StringListCreator {
75
+ public:
76
+ mutable int counter;
77
+
78
+ DummyStringListCreator() {
79
+ counter = 0;
80
+ }
81
+
82
+ virtual const StringListPtr getItems() const {
83
+ StringListPtr result = ptr(new StringList());
84
+ counter++;
85
+ result->push_back("hello");
86
+ result->push_back("world");
87
+ return result;
88
+ }
89
+ };
90
+
91
+ TEST_METHOD(5) {
92
+ // When calling get() with a PoolOptions object,
93
+ // options.environmentVariables->getItems() isn't called unless
94
+ // the pool had to spawn something.
95
+ ApplicationPoolServerPtr server = ptr(new ApplicationPoolServer(
96
+ "./ApplicationPoolServerExecutable",
97
+ "../bin/passenger-spawn-server"));
98
+ ApplicationPoolPtr pool = server->connect();
99
+
100
+ shared_ptr<DummyStringListCreator> strList = ptr(new DummyStringListCreator());
101
+ PoolOptions options("stub/rack");
102
+ options.appType = "rack";
103
+ options.environmentVariables = strList;
104
+
105
+ Application::SessionPtr session1 = pool->get(options);
106
+ session1.reset();
107
+ ensure_equals(strList->counter, 1);
108
+
109
+ session1 = pool->get(options);
110
+ session1.reset();
111
+ ensure_equals(strList->counter, 1);
112
+ }
71
113
  }
72
114
 
@@ -12,7 +12,8 @@
12
12
  * This file is used as a template to test the different ApplicationPool implementations.
13
13
  * It is #included in StandardApplicationPoolTest.cpp and ApplicationServer_ApplicationPoolTest.cpp
14
14
  */
15
- #ifdef USE_TEMPLATE
15
+ #ifdef USE_TEMPLATE
16
+ /** Create some stub request headers. */
16
17
  static string createRequestHeaders(const char *uri = "/foo/new") {
17
18
  string headers;
18
19
  #define ADD_HEADER(name, value) \
@@ -25,29 +26,11 @@
25
26
  ADD_HEADER("REQUEST_URI", uri);
26
27
  ADD_HEADER("REQUEST_METHOD", "GET");
27
28
  ADD_HEADER("REMOTE_ADDR", "localhost");
29
+ ADD_HEADER("SCRIPT_NAME", "");
28
30
  ADD_HEADER("PATH_INFO", uri);
29
31
  return headers;
30
32
  }
31
33
 
32
- static string readAll(int fd) {
33
- string result;
34
- char buf[1024 * 32];
35
- ssize_t ret;
36
- while (true) {
37
- do {
38
- ret = read(fd, buf, sizeof(buf));
39
- } while (ret == -1 && errno == EINTR);
40
- if (ret == 0) {
41
- break;
42
- } else if (ret == -1) {
43
- throw SystemException("Cannot read from socket", errno);
44
- } else {
45
- result.append(buf, ret);
46
- }
47
- }
48
- return result;
49
- }
50
-
51
34
  static Application::SessionPtr spawnRackApp(ApplicationPoolPtr pool, const char *appRoot) {
52
35
  PoolOptions options;
53
36
  options.appRoot = appRoot;
@@ -61,17 +44,17 @@
61
44
  options.appType = "wsgi";
62
45
  return pool->get(options);
63
46
  }
64
-
47
+
65
48
  TEST_METHOD(1) {
66
49
  // Calling ApplicationPool.get() once should return a valid Session.
67
- Application::SessionPtr session(pool->get("stub/railsapp"));
50
+ Application::SessionPtr session(spawnRackApp(pool, "stub/rack"));
68
51
  session->sendHeaders(createRequestHeaders());
69
52
  session->shutdownWriter();
70
53
 
71
54
  int reader = session->getStream();
72
55
  string result(readAll(reader));
73
56
  session->closeStream();
74
- ensure(result.find("hello world") != string::npos);
57
+ ensure(result.find("hello <b>world</b>") != string::npos);
75
58
  }
76
59
 
77
60
  TEST_METHOD(2) {
@@ -99,26 +82,29 @@
99
82
  // If we call get() with an application root, then we call get() again before closing
100
83
  // the session, then the pool should have spawned 2 apps in total.
101
84
  Application::SessionPtr session(spawnRackApp(pool, "stub/rack"));
102
- Application::SessionPtr session2(spawnRackApp(pool, "stub/rack"));
85
+ Application::SessionPtr session2(spawnRackApp(pool2, "stub/rack"));
103
86
  ensure_equals(pool->getCount(), 2u);
104
87
  }
105
88
 
106
89
  TEST_METHOD(5) {
107
90
  // If we call get() twice with different application roots,
108
91
  // then the pool should spawn two different apps.
109
- Application::SessionPtr session(pool->get("stub/railsapp"));
110
- Application::SessionPtr session2(pool2->get("stub/railsapp2"));
92
+ TempDirCopy c1("stub/rack", "rackapp1.tmp");
93
+ TempDirCopy c2("stub/rack", "rackapp2.tmp");
94
+ replaceStringInFile("rackapp2.tmp/config.ru", "world", "world 2");
95
+ Application::SessionPtr session(spawnRackApp(pool, "rackapp1.tmp"));
96
+ Application::SessionPtr session2(spawnRackApp(pool2, "rackapp2.tmp"));
111
97
  ensure_equals("Before the sessions were closed, both apps were busy", pool->getActive(), 2u);
112
98
  ensure_equals("Before the sessions were closed, both apps were in the pool", pool->getCount(), 2u);
113
99
 
114
100
  session->sendHeaders(createRequestHeaders());
115
101
  string result(readAll(session->getStream()));
116
- ensure("Session 1 belongs to the correct app", result.find("hello world") != string::npos);
102
+ ensure("Session 1 belongs to the correct app", result.find("hello <b>world</b>") != string::npos);
117
103
  session.reset();
118
104
 
119
105
  session2->sendHeaders(createRequestHeaders());
120
106
  result = readAll(session2->getStream());
121
- ensure("Session 2 belongs to the correct app", result.find("this is railsapp2") != string::npos);
107
+ ensure("Session 2 belongs to the correct app", result.find("hello <b>world 2</b>") != string::npos);
122
108
  session2.reset();
123
109
  }
124
110
 
@@ -126,12 +112,14 @@
126
112
  // If we call get() twice with different application roots,
127
113
  // and we close both sessions, then both 2 apps should still
128
114
  // be in the pool.
129
- Application::SessionPtr session(pool->get("stub/railsapp"));
130
- Application::SessionPtr session2(pool->get("stub/railsapp2"));
115
+ TempDirCopy c1("stub/rack", "rackapp1.tmp");
116
+ TempDirCopy c2("stub/rack", "rackapp2.tmp");
117
+ Application::SessionPtr session(spawnRackApp(pool, "rackapp1.tmp"));
118
+ Application::SessionPtr session2(spawnRackApp(pool, "rackapp2.tmp"));
131
119
  session.reset();
132
120
  session2.reset();
133
- ensure_equals(pool->getActive(), 0u);
134
- ensure_equals(pool->getCount(), 2u);
121
+ ensure_equals("There are 0 active apps", pool->getActive(), 0u);
122
+ ensure_equals("There are 2 apps in total", pool->getCount(), 2u);
135
123
  }
136
124
 
137
125
  TEST_METHOD(7) {
@@ -150,26 +138,26 @@
150
138
  // But the get() thereafter should not:
151
139
  // ApplicationPool should have spawned a new instance
152
140
  // after detecting that the original one died.
153
- Application::SessionPtr session(pool->get("stub/railsapp"));
141
+ Application::SessionPtr session(spawnRackApp(pool, "stub/rack"));
154
142
  kill(session->getPid(), SIGTERM);
155
143
  session.reset();
156
144
  try {
157
- session = pool->get("stub/railsapp");
145
+ session = spawnRackApp(pool, "stub/rack");
158
146
  fail("ApplicationPool::get() is supposed to "
159
147
  "throw an exception because we killed "
160
148
  "the app instance.");
161
149
  } catch (const exception &e) {
162
- session = pool->get("stub/railsapp");
150
+ session = spawnRackApp(pool, "stub/rack");
163
151
  // Should not throw.
164
152
  }
165
153
  }
166
154
 
167
- struct TestThread1 {
155
+ struct PoolWaitTestThread {
168
156
  ApplicationPoolPtr pool;
169
157
  Application::SessionPtr &m_session;
170
158
  bool &m_done;
171
159
 
172
- TestThread1(const ApplicationPoolPtr &pool,
160
+ PoolWaitTestThread(const ApplicationPoolPtr &pool,
173
161
  Application::SessionPtr &session,
174
162
  bool &done)
175
163
  : m_session(session), m_done(done) {
@@ -194,12 +182,13 @@
194
182
  Application::SessionPtr session3;
195
183
  bool done;
196
184
 
197
- thread *thr = new thread(TestThread1(pool2, session3, done));
185
+ shared_ptr<thread> thr = ptr(new thread(PoolWaitTestThread(pool2, session3, done)));
198
186
  usleep(500000);
199
- ensure("ApplicationPool is waiting", !done);
187
+ ensure("ApplicationPool is still waiting", !done);
200
188
  ensure_equals(pool->getActive(), 2u);
201
189
  ensure_equals(pool->getCount(), 2u);
202
190
 
191
+ // Now release one slot from the pool.
203
192
  session1.reset();
204
193
 
205
194
  // Wait at most 10 seconds.
@@ -213,7 +202,7 @@
213
202
  ensure_equals(pool->getCount(), 2u);
214
203
 
215
204
  thr->join();
216
- delete thr;
205
+ thr.reset();
217
206
  }
218
207
 
219
208
  TEST_METHOD(10) {
@@ -221,25 +210,27 @@
221
210
  // * the pool is already full, but there are inactive apps
222
211
  // (active < count && count == max)
223
212
  // and
224
- // * the application root is *not* already in the pool
213
+ // * the application root for this get() is *not* already in the pool
225
214
  // then the an inactive app should be killed in order to
226
215
  // satisfy this get() command.
216
+ TempDirCopy c1("stub/rack", "rackapp1.tmp");
217
+ TempDirCopy c2("stub/rack", "rackapp2.tmp");
227
218
  pool->setMax(2);
228
- Application::SessionPtr session1(pool->get("stub/railsapp"));
229
- Application::SessionPtr session2(pool->get("stub/railsapp"));
219
+ Application::SessionPtr session1(spawnRackApp(pool, "rackapp1.tmp"));
220
+ Application::SessionPtr session2(spawnRackApp(pool, "rackapp1.tmp"));
230
221
  session1.reset();
231
222
  session2.reset();
232
223
 
233
224
  ensure_equals(pool->getActive(), 0u);
234
225
  ensure_equals(pool->getCount(), 2u);
235
- session1 = pool2->get("stub/railsapp2");
226
+ session1 = spawnRackApp(pool, "rackapp2.tmp");
236
227
  ensure_equals(pool->getActive(), 1u);
237
228
  ensure_equals(pool->getCount(), 2u);
238
229
  }
239
230
 
240
231
  TEST_METHOD(11) {
241
- // Test whether Session is still usable after the Application has been destroyed.
242
- Application::SessionPtr session(pool->get("stub/railsapp"));
232
+ // A Session should still be usable after the pool has been destroyed.
233
+ Application::SessionPtr session(spawnRackApp(pool, "stub/rack"));
243
234
  pool->clear();
244
235
  pool.reset();
245
236
  pool2.reset();
@@ -250,32 +241,27 @@
250
241
  int reader = session->getStream();
251
242
  string result(readAll(reader));
252
243
  session->closeStream();
253
- ensure(result.find("hello world") != string::npos);
244
+ ensure(result.find("hello <b>world</b>") != string::npos);
254
245
  }
255
246
 
256
247
  TEST_METHOD(12) {
257
248
  // If tmp/restart.txt didn't exist but has now been created,
258
249
  // then the applications under app_root should be restarted.
259
250
  struct stat buf;
260
- Application::SessionPtr session1 = pool->get("stub/railsapp");
261
- Application::SessionPtr session2 = pool2->get("stub/railsapp");
251
+ TempDirCopy c("stub/rack", "rackapp.tmp");
252
+ Application::SessionPtr session1 = spawnRackApp(pool, "rackapp.tmp");
253
+ Application::SessionPtr session2 = spawnRackApp(pool, "rackapp.tmp");
262
254
  session1.reset();
263
255
  session2.reset();
264
256
 
265
- system("touch stub/railsapp/tmp/restart.txt");
266
- pool->get("stub/railsapp");
257
+ touchFile("rackapp.tmp/tmp/restart.txt");
258
+ spawnRackApp(pool, "rackapp.tmp");
267
259
 
268
260
  ensure_equals("No apps are active", pool->getActive(), 0u);
269
261
  ensure_equals("Both apps are killed, and a new one was spawned",
270
262
  pool->getCount(), 1u);
271
- try {
272
- ensure("Restart file still exists",
273
- stat("stub/railsapp/tmp/restart.txt", &buf) == 0);
274
- unlink("stub/railsapp/tmp/restart.txt");
275
- } catch (...) {
276
- unlink("stub/railsapp/tmp/restart.txt");
277
- throw;
278
- }
263
+ ensure("Restart file still exists",
264
+ stat("rackapp.tmp/tmp/restart.txt", &buf) == 0);
279
265
  }
280
266
 
281
267
  TEST_METHOD(13) {
@@ -284,56 +270,213 @@
284
270
  // should still be restarted. However, a subsequent get()
285
271
  // should not result in a restart.
286
272
  pid_t old_pid;
273
+ TempDirCopy c("stub/rack", "rackapp.tmp");
274
+ TempDir d("rackapp.tmp/tmp/restart.txt");
275
+ Application::SessionPtr session = spawnRackApp(pool, "rackapp.tmp");
276
+ old_pid = session->getPid();
277
+ session.reset();
287
278
 
288
- system("mkdir -p stub/railsapp/tmp/restart.txt");
289
- try {
290
- Application::SessionPtr session = pool->get("stub/railsapp");
291
- old_pid = session->getPid();
292
- session.reset();
293
-
294
- struct utimbuf buf;
295
- buf.actime = time(NULL) - 10;
296
- buf.modtime = time(NULL) - 10;
297
- utime("stub/railsapp/tmp/restart.txt", &buf);
298
-
299
- session = pool->get("stub/railsapp");
300
- ensure("The app was restarted", session->getPid() != old_pid);
301
- old_pid = session->getPid();
302
- session.reset();
303
-
304
- session = pool->get("stub/railsapp");
305
- ensure_equals("The app was not restarted",
306
- old_pid, session->getPid());
307
- } catch (...) {
308
- system("rmdir stub/railsapp/tmp/restart.txt");
309
- throw;
310
- }
279
+ touchFile("rackapp.tmp/tmp/restart.txt", 10);
280
+
281
+ session = spawnRackApp(pool, "rackapp.tmp");
282
+ ensure("The app was restarted", session->getPid() != old_pid);
283
+ old_pid = session->getPid();
284
+ session.reset();
285
+
286
+ session = spawnRackApp(pool, "rackapp.tmp");
287
+ ensure_equals("The app was not restarted",
288
+ old_pid, session->getPid());
311
289
  }
312
290
 
313
291
  TEST_METHOD(15) {
314
- // Test whether restarting really results in code reload.
315
- DeleteFileEventually f1("stub/railsapp/app/controllers/bar_controller.rb");
316
- DeleteFileEventually f2("stub/railsapp/tmp/restart.txt");
317
-
318
- system("cp -f stub/railsapp/app/controllers/bar_controller_1.txt "
319
- "stub/railsapp/app/controllers/bar_controller.rb");
320
- Application::SessionPtr session = pool->get("stub/railsapp");
321
- session->sendHeaders(createRequestHeaders("/bar"));
292
+ // Test whether restarting with restart.txt really results in code reload.
293
+ TempDirCopy c("stub/rack", "rackapp.tmp");
294
+ Application::SessionPtr session = spawnRackApp(pool, "rackapp.tmp");
295
+ session->sendHeaders(createRequestHeaders());
322
296
  string result = readAll(session->getStream());
323
- ensure(result.find("bar 1!") != string::npos);
297
+ ensure(result.find("hello <b>world</b>") != string::npos);
324
298
  session.reset();
325
299
 
326
- system("cp -f stub/railsapp/app/controllers/bar_controller_2.txt "
327
- "stub/railsapp/app/controllers/bar_controller.rb");
328
- system("touch stub/railsapp/tmp/restart.txt");
300
+ touchFile("rackapp.tmp/tmp/restart.txt");
301
+ replaceStringInFile("rackapp.tmp/config.ru", "world", "world 2");
329
302
 
330
- session = pool->get("stub/railsapp");
331
- session->sendHeaders(createRequestHeaders("/bar"));
303
+ session = spawnRackApp(pool, "rackapp.tmp");
304
+ session->sendHeaders(createRequestHeaders());
332
305
  result = readAll(session->getStream());
333
- ensure("App code has been reloaded", result.find("bar 2!") != string::npos);
306
+ ensure("App code has been reloaded", result.find("hello <b>world 2</b>") != string::npos);
334
307
  }
335
308
 
336
309
  TEST_METHOD(16) {
310
+ // If tmp/always_restart.txt is present and is a file,
311
+ // then the application under app_root should be always restarted.
312
+ struct stat buf;
313
+ pid_t old_pid;
314
+ TempDirCopy c("stub/rack", "rackapp.tmp");
315
+ Application::SessionPtr session1 = spawnRackApp(pool, "rackapp.tmp");
316
+ Application::SessionPtr session2 = spawnRackApp(pool2, "rackapp.tmp");
317
+ session1.reset();
318
+ session2.reset();
319
+
320
+ touchFile("rackapp.tmp/tmp/always_restart.txt");
321
+
322
+ // This get() results in a restart.
323
+ session1 = spawnRackApp(pool, "rackapp.tmp");
324
+ old_pid = session1->getPid();
325
+ session1.reset();
326
+ ensure_equals("First restart: no apps are active", pool->getActive(), 0u);
327
+ ensure_equals("First restart: the first 2 apps were killed, and a new one was spawned",
328
+ pool->getCount(), 1u);
329
+ ensure("always_restart file has not been deleted",
330
+ stat("rackapp.tmp/tmp/always_restart.txt", &buf) == 0);
331
+
332
+ // This get() results in a restart as well.
333
+ session1 = spawnRackApp(pool, "rackapp.tmp");
334
+ ensure(old_pid != session1->getPid());
335
+ session1.reset();
336
+ ensure_equals("Second restart: no apps are active", pool->getActive(), 0u);
337
+ ensure_equals("Second restart: the last app was killed, and a new one was spawned",
338
+ pool->getCount(), 1u);
339
+ ensure("always_restart file has not been deleted",
340
+ stat("rackapp.tmp/tmp/always_restart.txt", &buf) == 0);
341
+ }
342
+
343
+ TEST_METHOD(17) {
344
+ // If tmp/always_restart.txt is present and is a directory,
345
+ // then the application under app_root should be always restarted.
346
+ struct stat buf;
347
+ pid_t old_pid;
348
+ TempDirCopy c("stub/rack", "rackapp.tmp");
349
+ Application::SessionPtr session1 = spawnRackApp(pool, "rackapp.tmp");
350
+ Application::SessionPtr session2 = spawnRackApp(pool, "rackapp.tmp");
351
+ session1.reset();
352
+ session2.reset();
353
+
354
+ TempDir d("rackapp.tmp/tmp/always_restart.txt");
355
+
356
+ // This get() results in a restart.
357
+ session1 = spawnRackApp(pool, "rackapp.tmp");
358
+ old_pid = session1->getPid();
359
+ session1.reset();
360
+ ensure_equals("First restart: no apps are active", pool->getActive(), 0u);
361
+ ensure_equals("First restart: the first 2 apps were killed, and a new one was spawned",
362
+ pool->getCount(), 1u);
363
+ ensure("always_restart directory has not been deleted",
364
+ stat("rackapp.tmp/tmp/always_restart.txt", &buf) == 0);
365
+
366
+ // This get() results in a restart as well.
367
+ session1 = spawnRackApp(pool, "rackapp.tmp");
368
+ ensure(old_pid != session1->getPid());
369
+ session1.reset();
370
+ ensure_equals("Second restart: no apps are active", pool->getActive(), 0u);
371
+ ensure_equals("Second restart: the last app was killed, and a new one was spawned",
372
+ pool->getCount(), 1u);
373
+ ensure("always_restart directory has not been deleted",
374
+ stat("rackapp.tmp/tmp/always_restart.txt", &buf) == 0);
375
+ }
376
+
377
+ TEST_METHOD(18) {
378
+ // Test whether restarting with tmp/always_restart.txt really results in code reload.
379
+ TempDirCopy c("stub/rack", "rackapp.tmp");
380
+ Application::SessionPtr session = spawnRackApp(pool, "rackapp.tmp");
381
+ session->sendHeaders(createRequestHeaders());
382
+ string result = readAll(session->getStream());
383
+ ensure(result.find("hello <b>world</b>") != string::npos);
384
+ session.reset();
385
+
386
+ touchFile("rackapp.tmp/tmp/always_restart.txt");
387
+ replaceStringInFile("rackapp.tmp/config.ru", "world", "world 2");
388
+
389
+ session = spawnRackApp(pool, "rackapp.tmp");
390
+ session->sendHeaders(createRequestHeaders());
391
+ result = readAll(session->getStream());
392
+ ensure("App code has been reloaded (1)", result.find("hello <b>world 2</b>") != string::npos);
393
+ session.reset();
394
+
395
+ replaceStringInFile("rackapp.tmp/config.ru", "world 2", "world 3");
396
+ session = spawnRackApp(pool, "rackapp.tmp");
397
+ session->sendHeaders(createRequestHeaders());
398
+ result = readAll(session->getStream());
399
+ ensure("App code has been reloaded (2)", result.find("hello <b>world 3</b>") != string::npos);
400
+ session.reset();
401
+ }
402
+
403
+ TEST_METHOD(19) {
404
+ // If tmp/restart.txt and tmp/always_restart.txt are present,
405
+ // the application under app_root should still be restarted and
406
+ // both files must be kept.
407
+ pid_t old_pid, pid;
408
+ struct stat buf;
409
+ TempDirCopy c("stub/rack", "rackapp.tmp");
410
+ Application::SessionPtr session1 = spawnRackApp(pool, "rackapp.tmp");
411
+ Application::SessionPtr session2 = spawnRackApp(pool2, "rackapp.tmp");
412
+ session1.reset();
413
+ session2.reset();
414
+
415
+ touchFile("rackapp.tmp/tmp/restart.txt");
416
+ touchFile("rackapp.tmp/tmp/always_restart.txt");
417
+
418
+ old_pid = spawnRackApp(pool, "rackapp.tmp")->getPid();
419
+ ensure("always_restart.txt file has not been deleted",
420
+ stat("rackapp.tmp/tmp/always_restart.txt", &buf) == 0);
421
+ ensure("restart.txt file has not been deleted",
422
+ stat("rackapp.tmp/tmp/restart.txt", &buf) == 0);
423
+
424
+ pid = spawnRackApp(pool, "rackapp.tmp")->getPid();
425
+ ensure("The app was restarted", pid != old_pid);
426
+ }
427
+
428
+ TEST_METHOD(20) {
429
+ // It should look for restart.txt in the directory given by
430
+ // the restartDir option, if available.
431
+ struct stat buf;
432
+ char path[1024];
433
+ PoolOptions options("stub/rack");
434
+ options.appType = "rack";
435
+ options.restartDir = string(getcwd(path, sizeof(path))) + "/stub/rack";
436
+
437
+ Application::SessionPtr session1 = pool->get(options);
438
+ Application::SessionPtr session2 = pool2->get(options);
439
+ session1.reset();
440
+ session2.reset();
441
+
442
+ DeleteFileEventually f("stub/rack/restart.txt");
443
+ touchFile("stub/rack/restart.txt");
444
+
445
+ pool->get(options);
446
+
447
+ ensure_equals("No apps are active", pool->getActive(), 0u);
448
+ ensure_equals("Both apps are killed, and a new one was spawned",
449
+ pool->getCount(), 1u);
450
+ ensure("Restart file still exists",
451
+ stat("stub/rack/restart.txt", &buf) == 0);
452
+ }
453
+
454
+ TEST_METHOD(21) {
455
+ // restartDir may also be a directory relative to the
456
+ // application root.
457
+ struct stat buf;
458
+ PoolOptions options("stub/rack");
459
+ options.appType = "rack";
460
+ options.restartDir = "public";
461
+
462
+ Application::SessionPtr session1 = pool->get(options);
463
+ Application::SessionPtr session2 = pool2->get(options);
464
+ session1.reset();
465
+ session2.reset();
466
+
467
+ DeleteFileEventually f("stub/rack/public/restart.txt");
468
+ touchFile("stub/rack/public/restart.txt");
469
+
470
+ pool->get(options);
471
+
472
+ ensure_equals("No apps are active", pool->getActive(), 0u);
473
+ ensure_equals("Both apps are killed, and a new one was spawned",
474
+ pool->getCount(), 1u);
475
+ ensure("Restart file still exists",
476
+ stat("stub/rack/public/restart.txt", &buf) == 0);
477
+ }
478
+
479
+ TEST_METHOD(22) {
337
480
  // The cleaner thread should clean idle applications without crashing.
338
481
  pool->setMaxIdleTime(1);
339
482
  spawnRackApp(pool, "stub/rack");
@@ -345,7 +488,7 @@
345
488
  ensure_equals("App should have been cleaned up", pool->getCount(), 0u);
346
489
  }
347
490
 
348
- TEST_METHOD(17) {
491
+ TEST_METHOD(23) {
349
492
  // MaxPerApp is respected.
350
493
  pool->setMax(3);
351
494
  pool->setMaxPerApp(1);
@@ -359,18 +502,20 @@
359
502
 
360
503
  // We connect to stub/wsgi. Assert that the pool spawns a new
361
504
  // instance for this app.
505
+ TempDirCopy c("stub/wsgi", "wsgiapp.tmp");
362
506
  ApplicationPoolPtr pool3(newPoolConnection());
363
- Application::SessionPtr session3 = spawnWsgiApp(pool3, "stub/wsgi");
507
+ Application::SessionPtr session3 = spawnWsgiApp(pool3, "wsgiapp.tmp");
364
508
  ensure_equals(pool->getCount(), 2u);
365
509
  }
366
510
 
367
- TEST_METHOD(18) {
511
+ TEST_METHOD(24) {
368
512
  // Application instance is shutdown after 'maxRequests' requests.
369
- PoolOptions options("stub/railsapp");
513
+ PoolOptions options("stub/rack");
370
514
  int reader;
371
515
  pid_t originalPid;
372
516
  Application::SessionPtr session;
373
517
 
518
+ options.appType = "rack";
374
519
  options.maxRequests = 4;
375
520
  pool->setMax(1);
376
521
  session = pool->get(options);
@@ -411,7 +556,7 @@
411
556
  }
412
557
  };
413
558
 
414
- TEST_METHOD(19) {
559
+ TEST_METHOD(25) {
415
560
  // If global queueing mode is enabled, then get() waits until
416
561
  // there's at least one idle backend process for this application
417
562
  // domain.
@@ -439,171 +584,14 @@
439
584
  thr.join();
440
585
  }
441
586
 
442
- TEST_METHOD(20) {
443
- // If tmp/always_restart.txt is present, then the application under app_root
444
- // should be always restarted.
445
- try {
446
- struct stat buf;
447
- Application::SessionPtr session1 = pool->get("stub/railsapp");
448
- Application::SessionPtr session2 = pool2->get("stub/railsapp");
449
- session1.reset();
450
- session2.reset();
451
-
452
- system("touch stub/railsapp/tmp/always_restart.txt");
453
- pool->get("stub/railsapp");
454
-
455
- ensure_equals("No apps are active", pool->getActive(), 0u);
456
- ensure_equals("Both apps are killed, and a new one was spawned",
457
- pool->getCount(), 1u);
458
- ensure("always_restart file has not been deleted",
459
- stat("stub/railsapp/tmp/always_restart.txt", &buf) == 0);
460
- unlink("stub/railsapp/tmp/always_restart.txt");
461
- } catch (...) {
462
- unlink("stub/railsapp/tmp/always_restart.txt");
463
- throw;
464
- }
465
- }
466
-
467
- TEST_METHOD(21) {
468
- // If tmp/always_restart.txt is present and is a directory,
469
- // then the application under app_root
470
- // should be always restarted.
471
- try {
472
- struct stat buf;
473
- Application::SessionPtr session1 = pool->get("stub/railsapp");
474
- Application::SessionPtr session2 = pool2->get("stub/railsapp");
475
- session1.reset();
476
- session2.reset();
477
-
478
- system("mkdir stub/railsapp/tmp/always_restart.txt");
479
- pool->get("stub/railsapp");
480
-
481
- ensure_equals("No apps are active", pool->getActive(), 0u);
482
- ensure_equals("Both apps are killed, and a new one was spawned",
483
- pool->getCount(), 1u);
484
- ensure("always_restart file has not been deleted",
485
- stat("stub/railsapp/tmp/always_restart.txt", &buf) == 0);
486
- system("rmdir stub/railsapp/tmp/always_restart.txt");
487
- } catch (...) {
488
- system("rmdir stub/railsapp/tmp/always_restart.txt");
489
- throw;
490
- }
491
- }
492
-
493
- TEST_METHOD(22) {
494
- // Test whether tmp/always_restart.txt really results in code reload.
495
- DeleteFileEventually f1("stub/railsapp/app/controllers/bar_controller.rb");
496
- DeleteFileEventually f2("stub/railsapp/tmp/always_restart.txt");
497
-
498
- system("cp -f stub/railsapp/app/controllers/bar_controller_1.txt "
499
- "stub/railsapp/app/controllers/bar_controller.rb");
500
- Application::SessionPtr session = pool->get("stub/railsapp");
501
- session->sendHeaders(createRequestHeaders("/bar"));
502
- string result = readAll(session->getStream());
503
- ensure(result.find("bar 1!") != string::npos);
504
- session.reset();
505
-
506
- system("cp -f stub/railsapp/app/controllers/bar_controller_2.txt "
507
- "stub/railsapp/app/controllers/bar_controller.rb");
508
- system("touch stub/railsapp/tmp/always_restart.txt");
509
- session = pool->get("stub/railsapp");
510
- session->sendHeaders(createRequestHeaders("/bar"));
511
- result = readAll(session->getStream());
512
- ensure("App code has been reloaded (1)", result.find("bar 2!") != string::npos);
513
- session.reset();
514
-
515
- system("cp -f stub/railsapp/app/controllers/bar_controller_1.txt "
516
- "stub/railsapp/app/controllers/bar_controller.rb");
517
- session = pool->get("stub/railsapp");
518
- session->sendHeaders(createRequestHeaders("/bar"));
519
- result = readAll(session->getStream());
520
- ensure("App code has been reloaded (2)", result.find("bar 1!") != string::npos);
521
- session.reset();
522
- }
523
-
524
- TEST_METHOD(23) {
525
- // If tmp/restart.txt and tmp/always_restart.txt are present,
526
- // the application under app_root should still be restarted and
527
- // both files must be kept
528
- try {
529
- pid_t old_pid, pid;
530
- struct stat buf;
531
- Application::SessionPtr session1 = pool->get("stub/railsapp");
532
- Application::SessionPtr session2 = pool2->get("stub/railsapp");
533
- session1.reset();
534
- session2.reset();
535
-
536
- system("touch stub/railsapp/tmp/restart.txt");
537
- system("touch stub/railsapp/tmp/always_restart.txt");
538
-
539
- old_pid = pool->get("stub/railsapp")->getPid();
540
- ensure("always_restart file has not been deleted",
541
- stat("stub/railsapp/tmp/always_restart.txt", &buf) == 0);
542
-
543
- ensure("Restart file has not been deleted",
544
- stat("stub/railsapp/tmp/restart.txt", &buf) == 0);
545
-
546
- pid = pool->get("stub/railsapp")->getPid();
547
- ensure("The app was restarted", pid != old_pid);
548
-
549
- unlink("stub/railsapp/tmp/restart.txt");
550
- unlink("stub/railsapp/tmp/always_restart.txt");
551
- } catch (...) {
552
- unlink("stub/railsapp/tmp/restart.txt");
553
- unlink("stub/railsapp/tmp/always_restart.txt");
554
- throw;
555
- }
556
- }
557
-
558
- TEST_METHOD(24) {
559
- // It should look for restart.txt in the directory given by
560
- // the restartDir option, if available.
561
- struct stat buf;
562
- char path[1024];
563
- PoolOptions options("stub/rack");
564
- options.appType = "rack";
565
- options.restartDir = string(getcwd(path, sizeof(path))) + "/stub/rack";
566
-
567
- Application::SessionPtr session1 = pool->get(options);
568
- Application::SessionPtr session2 = pool2->get(options);
569
- session1.reset();
570
- session2.reset();
571
-
572
- DeleteFileEventually f("stub/rack/restart.txt");
573
- system("touch stub/rack/restart.txt");
574
-
575
- pool->get(options);
576
-
577
- ensure_equals("No apps are active", pool->getActive(), 0u);
578
- ensure_equals("Both apps are killed, and a new one was spawned",
579
- pool->getCount(), 1u);
580
- ensure("Restart file still exists",
581
- stat("stub/rack/restart.txt", &buf) == 0);
582
- }
583
-
584
- TEST_METHOD(25) {
585
- // restartDir may also be a directory relative to the
586
- // application root.
587
- struct stat buf;
588
- PoolOptions options("stub/rack");
589
- options.appType = "rack";
590
- options.restartDir = "public";
591
-
592
- Application::SessionPtr session1 = pool->get(options);
593
- Application::SessionPtr session2 = pool2->get(options);
594
- session1.reset();
595
- session2.reset();
596
-
597
- DeleteFileEventually f("stub/rack/public/restart.txt");
598
- system("touch stub/rack/public/restart.txt");
599
-
600
- pool->get(options);
601
-
602
- ensure_equals("No apps are active", pool->getActive(), 0u);
603
- ensure_equals("Both apps are killed, and a new one was spawned",
604
- pool->getCount(), 1u);
605
- ensure("Restart file still exists",
606
- stat("stub/rack/public/restart.txt", &buf) == 0);
587
+ TEST_METHOD(26) {
588
+ // When a previous application domain spinned down, and we touched
589
+ // restart.txt and try to spin up a new process for this domain,
590
+ // then any ApplicationSpawner/FrameworkSpawner processes should be
591
+ // killed first.
592
+
593
+ // TODO: to test this we first need to be able to move
594
+ // ApplicationPoolServer in-process so that we can use mock objects
607
595
  }
608
596
 
609
597
  /*************************************/