unicorn-heroku 4.3.1.1.gc608.dirty

Sign up to get free protection for your applications and to get access to all the features.
Files changed (247) hide show
  1. data/.CHANGELOG.old +25 -0
  2. data/.document +29 -0
  3. data/.gitignore +24 -0
  4. data/.mailmap +26 -0
  5. data/.wrongdoc.yml +10 -0
  6. data/Application_Timeouts +77 -0
  7. data/CONTRIBUTORS +35 -0
  8. data/COPYING +674 -0
  9. data/DESIGN +97 -0
  10. data/Documentation/.gitignore +5 -0
  11. data/Documentation/GNUmakefile +30 -0
  12. data/Documentation/unicorn.1.txt +174 -0
  13. data/Documentation/unicorn_rails.1.txt +175 -0
  14. data/FAQ +53 -0
  15. data/GIT-VERSION-GEN +40 -0
  16. data/GNUmakefile +294 -0
  17. data/HACKING +134 -0
  18. data/ISSUES +36 -0
  19. data/KNOWN_ISSUES +79 -0
  20. data/LICENSE +64 -0
  21. data/Links +56 -0
  22. data/PHILOSOPHY +145 -0
  23. data/README +154 -0
  24. data/Rakefile +97 -0
  25. data/SIGNALS +114 -0
  26. data/Sandbox +96 -0
  27. data/TODO +5 -0
  28. data/TUNING +98 -0
  29. data/bin/unicorn +121 -0
  30. data/bin/unicorn_rails +209 -0
  31. data/examples/big_app_gc.rb +2 -0
  32. data/examples/echo.ru +27 -0
  33. data/examples/git.ru +13 -0
  34. data/examples/init.sh +74 -0
  35. data/examples/logger_mp_safe.rb +25 -0
  36. data/examples/logrotate.conf +29 -0
  37. data/examples/nginx.conf +156 -0
  38. data/examples/unicorn.conf.minimal.rb +13 -0
  39. data/examples/unicorn.conf.rb +94 -0
  40. data/ext/unicorn_http/CFLAGS +13 -0
  41. data/ext/unicorn_http/c_util.h +124 -0
  42. data/ext/unicorn_http/common_field_optimization.h +111 -0
  43. data/ext/unicorn_http/ext_help.h +86 -0
  44. data/ext/unicorn_http/extconf.rb +10 -0
  45. data/ext/unicorn_http/global_variables.h +97 -0
  46. data/ext/unicorn_http/httpdate.c +82 -0
  47. data/ext/unicorn_http/unicorn_http.rl +1036 -0
  48. data/ext/unicorn_http/unicorn_http_common.rl +76 -0
  49. data/lib/unicorn.rb +107 -0
  50. data/lib/unicorn/app/exec_cgi.rb +154 -0
  51. data/lib/unicorn/app/inetd.rb +109 -0
  52. data/lib/unicorn/app/old_rails.rb +35 -0
  53. data/lib/unicorn/app/old_rails/static.rb +59 -0
  54. data/lib/unicorn/cgi_wrapper.rb +147 -0
  55. data/lib/unicorn/configurator.rb +630 -0
  56. data/lib/unicorn/const.rb +40 -0
  57. data/lib/unicorn/http_request.rb +77 -0
  58. data/lib/unicorn/http_response.rb +45 -0
  59. data/lib/unicorn/http_server.rb +744 -0
  60. data/lib/unicorn/launcher.rb +62 -0
  61. data/lib/unicorn/oob_gc.rb +71 -0
  62. data/lib/unicorn/preread_input.rb +33 -0
  63. data/lib/unicorn/socket_helper.rb +208 -0
  64. data/lib/unicorn/ssl_client.rb +11 -0
  65. data/lib/unicorn/ssl_configurator.rb +104 -0
  66. data/lib/unicorn/ssl_server.rb +42 -0
  67. data/lib/unicorn/stream_input.rb +149 -0
  68. data/lib/unicorn/tee_input.rb +126 -0
  69. data/lib/unicorn/tmpio.rb +29 -0
  70. data/lib/unicorn/util.rb +68 -0
  71. data/lib/unicorn/worker.rb +88 -0
  72. data/local.mk.sample +59 -0
  73. data/script/isolate_for_tests +50 -0
  74. data/setup.rb +1586 -0
  75. data/t/.gitignore +5 -0
  76. data/t/GNUmakefile +82 -0
  77. data/t/README +42 -0
  78. data/t/bin/content-md5-put +36 -0
  79. data/t/bin/sha1sum.rb +17 -0
  80. data/t/bin/unused_listen +40 -0
  81. data/t/bin/utee +12 -0
  82. data/t/broken-app.ru +12 -0
  83. data/t/detach.ru +11 -0
  84. data/t/env.ru +3 -0
  85. data/t/heartbeat-timeout.ru +12 -0
  86. data/t/my-tap-lib.sh +201 -0
  87. data/t/oob_gc.ru +21 -0
  88. data/t/oob_gc_path.ru +21 -0
  89. data/t/pid.ru +3 -0
  90. data/t/preread_input.ru +17 -0
  91. data/t/rack-input-tests.ru +21 -0
  92. data/t/rails3-app/.gitignore +4 -0
  93. data/t/rails3-app/Gemfile +26 -0
  94. data/t/rails3-app/Rakefile +10 -0
  95. data/t/rails3-app/app/controllers/application_controller.rb +4 -0
  96. data/t/rails3-app/app/helpers/application_helper.rb +2 -0
  97. data/t/rails3-app/app/views/layouts/application.html.erb +14 -0
  98. data/t/rails3-app/config.ru +4 -0
  99. data/t/rails3-app/config/application.rb +46 -0
  100. data/t/rails3-app/config/boot.rb +6 -0
  101. data/t/rails3-app/config/database.yml +22 -0
  102. data/t/rails3-app/config/environment.rb +5 -0
  103. data/t/rails3-app/config/environments/development.rb +19 -0
  104. data/t/rails3-app/config/environments/production.rb +42 -0
  105. data/t/rails3-app/config/environments/test.rb +32 -0
  106. data/t/rails3-app/config/initializers/backtrace_silencers.rb +7 -0
  107. data/t/rails3-app/config/initializers/inflections.rb +10 -0
  108. data/t/rails3-app/config/initializers/mime_types.rb +5 -0
  109. data/t/rails3-app/config/initializers/secret_token.rb +7 -0
  110. data/t/rails3-app/config/initializers/session_store.rb +8 -0
  111. data/t/rails3-app/config/locales/en.yml +5 -0
  112. data/t/rails3-app/config/routes.rb +58 -0
  113. data/t/rails3-app/db/seeds.rb +7 -0
  114. data/t/rails3-app/doc/README_FOR_APP +2 -0
  115. data/t/rails3-app/lib/tasks/.gitkeep +0 -0
  116. data/t/rails3-app/public/404.html +1 -0
  117. data/t/rails3-app/public/500.html +1 -0
  118. data/t/rails3-app/public/x.txt +1 -0
  119. data/t/rails3-app/script/rails +9 -0
  120. data/t/rails3-app/test/performance/browsing_test.rb +9 -0
  121. data/t/rails3-app/test/test_helper.rb +13 -0
  122. data/t/rails3-app/vendor/plugins/.gitkeep +0 -0
  123. data/t/sslgen.sh +71 -0
  124. data/t/t0000-http-basic.sh +50 -0
  125. data/t/t0001-reload-bad-config.sh +53 -0
  126. data/t/t0002-config-conflict.sh +49 -0
  127. data/t/t0002-parser-error.sh +94 -0
  128. data/t/t0003-working_directory.sh +51 -0
  129. data/t/t0004-heartbeat-timeout.sh +69 -0
  130. data/t/t0004-working_directory_broken.sh +24 -0
  131. data/t/t0005-working_directory_app.rb.sh +37 -0
  132. data/t/t0006-reopen-logs.sh +83 -0
  133. data/t/t0006.ru +13 -0
  134. data/t/t0007-working_directory_no_embed_cli.sh +44 -0
  135. data/t/t0008-back_out_of_upgrade.sh +110 -0
  136. data/t/t0009-broken-app.sh +56 -0
  137. data/t/t0009-winch_ttin.sh +59 -0
  138. data/t/t0010-reap-logging.sh +55 -0
  139. data/t/t0011-active-unix-socket.sh +79 -0
  140. data/t/t0012-reload-empty-config.sh +85 -0
  141. data/t/t0013-rewindable-input-false.sh +24 -0
  142. data/t/t0013.ru +12 -0
  143. data/t/t0014-rewindable-input-true.sh +24 -0
  144. data/t/t0014.ru +12 -0
  145. data/t/t0015-configurator-internals.sh +25 -0
  146. data/t/t0016-trust-x-forwarded-false.sh +30 -0
  147. data/t/t0017-trust-x-forwarded-true.sh +30 -0
  148. data/t/t0018-write-on-close.sh +23 -0
  149. data/t/t0019-max_header_len.sh +49 -0
  150. data/t/t0020-at_exit-handler.sh +49 -0
  151. data/t/t0021-process_detach.sh +29 -0
  152. data/t/t0100-rack-input-tests.sh +124 -0
  153. data/t/t0116-client_body_buffer_size.sh +80 -0
  154. data/t/t0116.ru +16 -0
  155. data/t/t0300-rails3-basic.sh +28 -0
  156. data/t/t0301-rails3-missing-config-ru.sh +33 -0
  157. data/t/t0302-rails3-alt-working_directory.sh +32 -0
  158. data/t/t0303-rails3-alt-working_directory_config.ru.sh +56 -0
  159. data/t/t0304-rails3-alt-working_directory_no_embed_cli.sh +52 -0
  160. data/t/t0600-https-server-basic.sh +48 -0
  161. data/t/t9000-preread-input.sh +48 -0
  162. data/t/t9001-oob_gc.sh +47 -0
  163. data/t/t9002-oob_gc-path.sh +75 -0
  164. data/t/test-lib.sh +113 -0
  165. data/t/test-rails3.sh +27 -0
  166. data/t/write-on-close.ru +11 -0
  167. data/test/aggregate.rb +15 -0
  168. data/test/benchmark/README +50 -0
  169. data/test/benchmark/dd.ru +18 -0
  170. data/test/benchmark/stack.ru +8 -0
  171. data/test/exec/README +5 -0
  172. data/test/exec/test_exec.rb +1055 -0
  173. data/test/rails/app-1.2.3/.gitignore +2 -0
  174. data/test/rails/app-1.2.3/Rakefile +7 -0
  175. data/test/rails/app-1.2.3/app/controllers/application.rb +6 -0
  176. data/test/rails/app-1.2.3/app/controllers/foo_controller.rb +36 -0
  177. data/test/rails/app-1.2.3/app/helpers/application_helper.rb +4 -0
  178. data/test/rails/app-1.2.3/config/boot.rb +11 -0
  179. data/test/rails/app-1.2.3/config/database.yml +12 -0
  180. data/test/rails/app-1.2.3/config/environment.rb +13 -0
  181. data/test/rails/app-1.2.3/config/environments/development.rb +9 -0
  182. data/test/rails/app-1.2.3/config/environments/production.rb +5 -0
  183. data/test/rails/app-1.2.3/config/routes.rb +6 -0
  184. data/test/rails/app-1.2.3/db/.gitignore +0 -0
  185. data/test/rails/app-1.2.3/public/404.html +1 -0
  186. data/test/rails/app-1.2.3/public/500.html +1 -0
  187. data/test/rails/app-2.0.2/.gitignore +2 -0
  188. data/test/rails/app-2.0.2/Rakefile +7 -0
  189. data/test/rails/app-2.0.2/app/controllers/application.rb +4 -0
  190. data/test/rails/app-2.0.2/app/controllers/foo_controller.rb +36 -0
  191. data/test/rails/app-2.0.2/app/helpers/application_helper.rb +4 -0
  192. data/test/rails/app-2.0.2/config/boot.rb +11 -0
  193. data/test/rails/app-2.0.2/config/database.yml +12 -0
  194. data/test/rails/app-2.0.2/config/environment.rb +17 -0
  195. data/test/rails/app-2.0.2/config/environments/development.rb +8 -0
  196. data/test/rails/app-2.0.2/config/environments/production.rb +5 -0
  197. data/test/rails/app-2.0.2/config/routes.rb +6 -0
  198. data/test/rails/app-2.0.2/db/.gitignore +0 -0
  199. data/test/rails/app-2.0.2/public/404.html +1 -0
  200. data/test/rails/app-2.0.2/public/500.html +1 -0
  201. data/test/rails/app-2.1.2/.gitignore +2 -0
  202. data/test/rails/app-2.1.2/Rakefile +7 -0
  203. data/test/rails/app-2.1.2/app/controllers/application.rb +4 -0
  204. data/test/rails/app-2.1.2/app/controllers/foo_controller.rb +36 -0
  205. data/test/rails/app-2.1.2/app/helpers/application_helper.rb +4 -0
  206. data/test/rails/app-2.1.2/config/boot.rb +111 -0
  207. data/test/rails/app-2.1.2/config/database.yml +12 -0
  208. data/test/rails/app-2.1.2/config/environment.rb +17 -0
  209. data/test/rails/app-2.1.2/config/environments/development.rb +7 -0
  210. data/test/rails/app-2.1.2/config/environments/production.rb +5 -0
  211. data/test/rails/app-2.1.2/config/routes.rb +6 -0
  212. data/test/rails/app-2.1.2/db/.gitignore +0 -0
  213. data/test/rails/app-2.1.2/public/404.html +1 -0
  214. data/test/rails/app-2.1.2/public/500.html +1 -0
  215. data/test/rails/app-2.2.2/.gitignore +2 -0
  216. data/test/rails/app-2.2.2/Rakefile +7 -0
  217. data/test/rails/app-2.2.2/app/controllers/application.rb +4 -0
  218. data/test/rails/app-2.2.2/app/controllers/foo_controller.rb +36 -0
  219. data/test/rails/app-2.2.2/app/helpers/application_helper.rb +4 -0
  220. data/test/rails/app-2.2.2/config/boot.rb +111 -0
  221. data/test/rails/app-2.2.2/config/database.yml +12 -0
  222. data/test/rails/app-2.2.2/config/environment.rb +17 -0
  223. data/test/rails/app-2.2.2/config/environments/development.rb +7 -0
  224. data/test/rails/app-2.2.2/config/environments/production.rb +5 -0
  225. data/test/rails/app-2.2.2/config/routes.rb +6 -0
  226. data/test/rails/app-2.2.2/db/.gitignore +0 -0
  227. data/test/rails/app-2.2.2/public/404.html +1 -0
  228. data/test/rails/app-2.2.2/public/500.html +1 -0
  229. data/test/rails/test_rails.rb +287 -0
  230. data/test/test_helper.rb +300 -0
  231. data/test/unit/test_configurator.rb +158 -0
  232. data/test/unit/test_droplet.rb +28 -0
  233. data/test/unit/test_http_parser.rb +860 -0
  234. data/test/unit/test_http_parser_ng.rb +716 -0
  235. data/test/unit/test_http_parser_xftrust.rb +38 -0
  236. data/test/unit/test_request.rb +197 -0
  237. data/test/unit/test_response.rb +99 -0
  238. data/test/unit/test_server.rb +289 -0
  239. data/test/unit/test_signals.rb +207 -0
  240. data/test/unit/test_sni_hostnames.rb +47 -0
  241. data/test/unit/test_socket_helper.rb +192 -0
  242. data/test/unit/test_stream_input.rb +204 -0
  243. data/test/unit/test_tee_input.rb +296 -0
  244. data/test/unit/test_upload.rb +306 -0
  245. data/test/unit/test_util.rb +100 -0
  246. data/unicorn-heroku.gemspec +44 -0
  247. metadata +426 -0
@@ -0,0 +1,8 @@
1
+ run(lambda { |env|
2
+ body = "#{caller.size}\n"
3
+ h = {
4
+ "Content-Length" => body.size.to_s,
5
+ "Content-Type" => "text/plain",
6
+ }
7
+ [ 200, h, [ body ] ]
8
+ })
@@ -0,0 +1,5 @@
1
+ These tests require the "unicorn" executable script to be installed in
2
+ PATH and rack being directly "require"-able ("rubygems" will not be
3
+ loaded for you). The tester is responsible for setting up RUBYLIB and
4
+ PATH environment variables (or running tests via GNU Make instead of
5
+ Rake).
@@ -0,0 +1,1055 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # Copyright (c) 2009 Eric Wong
4
+ FLOCK_PATH = File.expand_path(__FILE__)
5
+ require 'test/test_helper'
6
+
7
+ do_test = true
8
+ $unicorn_bin = ENV['UNICORN_TEST_BIN'] || "unicorn"
9
+ redirect_test_io do
10
+ do_test = system($unicorn_bin, '-v')
11
+ end
12
+
13
+ unless do_test
14
+ warn "#{$unicorn_bin} not found in PATH=#{ENV['PATH']}, " \
15
+ "skipping this test"
16
+ end
17
+
18
+ unless try_require('rack')
19
+ warn "Unable to load Rack, skipping this test"
20
+ do_test = false
21
+ end
22
+
23
+ class ExecTest < Test::Unit::TestCase
24
+ trap(:TERM, 'IGNORE')
25
+
26
+ HI = <<-EOS
27
+ use Rack::ContentLength
28
+ run proc { |env| [ 200, { 'Content-Type' => 'text/plain' }, [ "HI\\n" ] ] }
29
+ EOS
30
+
31
+ SHOW_RACK_ENV = <<-EOS
32
+ use Rack::ContentLength
33
+ run proc { |env|
34
+ [ 200, { 'Content-Type' => 'text/plain' }, [ ENV['RACK_ENV'] ] ]
35
+ }
36
+ EOS
37
+
38
+ HELLO = <<-EOS
39
+ class Hello
40
+ def call(env)
41
+ [ 200, { 'Content-Type' => 'text/plain' }, [ "HI\\n" ] ]
42
+ end
43
+ end
44
+ EOS
45
+
46
+ COMMON_TMP = Tempfile.new('unicorn_tmp') unless defined?(COMMON_TMP)
47
+
48
+ HEAVY_CFG = <<-EOS
49
+ worker_processes 4
50
+ timeout 30
51
+ logger Logger.new('#{COMMON_TMP.path}')
52
+ before_fork do |server, worker|
53
+ server.logger.info "before_fork: worker=\#{worker.nr}"
54
+ end
55
+ EOS
56
+
57
+ WORKING_DIRECTORY_CHECK_RU = <<-EOS
58
+ use Rack::ContentLength
59
+ run lambda { |env|
60
+ pwd = ENV['PWD']
61
+ a = ::File.stat(pwd)
62
+ b = ::File.stat(Dir.pwd)
63
+ if (a.ino == b.ino && a.dev == b.dev)
64
+ [ 200, { 'Content-Type' => 'text/plain' }, [ pwd ] ]
65
+ else
66
+ [ 404, { 'Content-Type' => 'text/plain' }, [] ]
67
+ end
68
+ }
69
+ EOS
70
+
71
+ def setup
72
+ @pwd = Dir.pwd
73
+ @tmpfile = Tempfile.new('unicorn_exec_test')
74
+ @tmpdir = @tmpfile.path
75
+ @tmpfile.close!
76
+ Dir.mkdir(@tmpdir)
77
+ Dir.chdir(@tmpdir)
78
+ @addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
79
+ @port = unused_port(@addr)
80
+ @sockets = []
81
+ @start_pid = $$
82
+ end
83
+
84
+ def teardown
85
+ return if @start_pid != $$
86
+ Dir.chdir(@pwd)
87
+ FileUtils.rmtree(@tmpdir)
88
+ @sockets.each { |path| File.unlink(path) rescue nil }
89
+ loop do
90
+ Process.kill('-TERM', 0)
91
+ begin
92
+ Process.waitpid(-1, Process::WNOHANG) or break
93
+ rescue Errno::ECHILD
94
+ break
95
+ end
96
+ end
97
+ end
98
+
99
+ def test_working_directory_rel_path_config_file
100
+ other = Tempfile.new('unicorn.wd')
101
+ File.unlink(other.path)
102
+ Dir.mkdir(other.path)
103
+ File.open("config.ru", "wb") do |fp|
104
+ fp.syswrite WORKING_DIRECTORY_CHECK_RU
105
+ end
106
+ FileUtils.cp("config.ru", other.path + "/config.ru")
107
+ Dir.chdir(@tmpdir)
108
+
109
+ tmp = File.open('unicorn.config', 'wb')
110
+ tmp.syswrite <<EOF
111
+ working_directory '#@tmpdir'
112
+ listen '#@addr:#@port'
113
+ EOF
114
+ pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } }
115
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
116
+ results = hit(["http://#@addr:#@port/"])
117
+ assert_equal @tmpdir, results.first
118
+ File.truncate("test_stderr.#{pid}.log", 0)
119
+
120
+ tmp.sysseek(0)
121
+ tmp.truncate(0)
122
+ tmp.syswrite <<EOF
123
+ working_directory '#{other.path}'
124
+ listen '#@addr:#@port'
125
+ EOF
126
+
127
+ Process.kill(:HUP, pid)
128
+ lines = []
129
+ re = /config_file=(.+) would not be accessible in working_directory=(.+)/
130
+ until lines.grep(re)
131
+ sleep 0.1
132
+ lines = File.readlines("test_stderr.#{pid}.log")
133
+ end
134
+
135
+ File.truncate("test_stderr.#{pid}.log", 0)
136
+ FileUtils.cp('unicorn.config', other.path + "/unicorn.config")
137
+ Process.kill(:HUP, pid)
138
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
139
+ results = hit(["http://#@addr:#@port/"])
140
+ assert_equal other.path, results.first
141
+
142
+ Process.kill(:TERM, pid)
143
+ ensure
144
+ FileUtils.rmtree(other.path)
145
+ end
146
+
147
+ def test_working_directory
148
+ other = Tempfile.new('unicorn.wd')
149
+ File.unlink(other.path)
150
+ Dir.mkdir(other.path)
151
+ File.open("config.ru", "wb") do |fp|
152
+ fp.syswrite WORKING_DIRECTORY_CHECK_RU
153
+ end
154
+ FileUtils.cp("config.ru", other.path + "/config.ru")
155
+ tmp = Tempfile.new('unicorn.config')
156
+ tmp.syswrite <<EOF
157
+ working_directory '#@tmpdir'
158
+ listen '#@addr:#@port'
159
+ EOF
160
+ pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } }
161
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
162
+ results = hit(["http://#@addr:#@port/"])
163
+ assert_equal @tmpdir, results.first
164
+ File.truncate("test_stderr.#{pid}.log", 0)
165
+
166
+ tmp.sysseek(0)
167
+ tmp.truncate(0)
168
+ tmp.syswrite <<EOF
169
+ working_directory '#{other.path}'
170
+ listen '#@addr:#@port'
171
+ EOF
172
+
173
+ Process.kill(:HUP, pid)
174
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
175
+ results = hit(["http://#@addr:#@port/"])
176
+ assert_equal other.path, results.first
177
+
178
+ Process.kill(:TERM, pid)
179
+ ensure
180
+ FileUtils.rmtree(other.path)
181
+ end
182
+
183
+ def test_working_directory_controls_relative_paths
184
+ other = Tempfile.new('unicorn.wd')
185
+ File.unlink(other.path)
186
+ Dir.mkdir(other.path)
187
+ File.open("config.ru", "wb") do |fp|
188
+ fp.syswrite WORKING_DIRECTORY_CHECK_RU
189
+ end
190
+ FileUtils.cp("config.ru", other.path + "/config.ru")
191
+ system('mkfifo', "#{other.path}/fifo")
192
+ tmp = Tempfile.new('unicorn.config')
193
+ tmp.syswrite <<EOF
194
+ pid "pid_file_here"
195
+ stderr_path "stderr_log_here"
196
+ stdout_path "stdout_log_here"
197
+ working_directory '#{other.path}'
198
+ listen '#@addr:#@port'
199
+ after_fork do |server, worker|
200
+ File.open("fifo", "wb").close
201
+ end
202
+ EOF
203
+ pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } }
204
+ File.open("#{other.path}/fifo", "rb").close
205
+
206
+ assert ! File.exist?("stderr_log_here")
207
+ assert ! File.exist?("stdout_log_here")
208
+ assert ! File.exist?("pid_file_here")
209
+
210
+ assert ! File.exist?("#@tmpdir/stderr_log_here")
211
+ assert ! File.exist?("#@tmpdir/stdout_log_here")
212
+ assert ! File.exist?("#@tmpdir/pid_file_here")
213
+
214
+ assert File.exist?("#{other.path}/pid_file_here")
215
+ assert_equal "#{pid}\n", File.read("#{other.path}/pid_file_here")
216
+ assert File.exist?("#{other.path}/stderr_log_here")
217
+ assert File.exist?("#{other.path}/stdout_log_here")
218
+ wait_master_ready("#{other.path}/stderr_log_here")
219
+
220
+ Process.kill(:TERM, pid)
221
+ ensure
222
+ FileUtils.rmtree(other.path)
223
+ end
224
+
225
+
226
+ def test_exit_signals
227
+ %w(INT TERM QUIT).each do |sig|
228
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
229
+ pid = xfork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
230
+ wait_master_ready("test_stderr.#{pid}.log")
231
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
232
+ status = nil
233
+ assert_nothing_raised do
234
+ Process.kill(sig, pid)
235
+ pid, status = Process.waitpid2(pid)
236
+ end
237
+ reaped = File.readlines("test_stderr.#{pid}.log").grep(/reaped/)
238
+ assert_equal 1, reaped.size
239
+ assert status.exited?
240
+ end
241
+ end
242
+
243
+ def test_basic
244
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
245
+ pid = fork do
246
+ redirect_test_io { exec($unicorn_bin, "-l", "#{@addr}:#{@port}") }
247
+ end
248
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
249
+ assert_equal String, results[0].class
250
+ assert_shutdown(pid)
251
+ end
252
+
253
+ def test_rack_env_unset
254
+ File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
255
+ pid = fork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
256
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
257
+ assert_equal "development", results.first
258
+ assert_shutdown(pid)
259
+ end
260
+
261
+ def test_rack_env_cli_set
262
+ File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
263
+ pid = fork {
264
+ redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port", "-Easdf") }
265
+ }
266
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
267
+ assert_equal "asdf", results.first
268
+ assert_shutdown(pid)
269
+ end
270
+
271
+ def test_rack_env_ENV_set
272
+ File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
273
+ pid = fork {
274
+ ENV["RACK_ENV"] = "foobar"
275
+ redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") }
276
+ }
277
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
278
+ assert_equal "foobar", results.first
279
+ assert_shutdown(pid)
280
+ end
281
+
282
+ def test_rack_env_cli_override_ENV
283
+ File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
284
+ pid = fork {
285
+ ENV["RACK_ENV"] = "foobar"
286
+ redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port", "-Easdf") }
287
+ }
288
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
289
+ assert_equal "asdf", results.first
290
+ assert_shutdown(pid)
291
+ end
292
+
293
+ def test_ttin_ttou
294
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
295
+ pid = fork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
296
+ log = "test_stderr.#{pid}.log"
297
+ wait_master_ready(log)
298
+ [ 2, 3].each { |i|
299
+ assert_nothing_raised { Process.kill(:TTIN, pid) }
300
+ wait_workers_ready(log, i)
301
+ }
302
+ File.truncate(log, 0)
303
+ reaped = nil
304
+ [ 2, 1, 0].each { |i|
305
+ assert_nothing_raised { Process.kill(:TTOU, pid) }
306
+ DEFAULT_TRIES.times {
307
+ sleep DEFAULT_RES
308
+ reaped = File.readlines(log).grep(/reaped.*\s*worker=#{i}$/)
309
+ break if reaped.size == 1
310
+ }
311
+ assert_equal 1, reaped.size
312
+ }
313
+ end
314
+
315
+ def test_help
316
+ redirect_test_io do
317
+ assert(system($unicorn_bin, "-h"), "help text returns true")
318
+ end
319
+ assert_equal 0, File.stat("test_stderr.#$$.log").size
320
+ assert_not_equal 0, File.stat("test_stdout.#$$.log").size
321
+ lines = File.readlines("test_stdout.#$$.log")
322
+
323
+ # Be considerate of the on-call technician working from their
324
+ # mobile phone or netbook on a slow connection :)
325
+ assert lines.size <= 24, "help height fits in an ANSI terminal window"
326
+ lines.each do |line|
327
+ assert line.size <= 80, "help width fits in an ANSI terminal window"
328
+ end
329
+ end
330
+
331
+ def test_broken_reexec_config
332
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
333
+ pid_file = "#{@tmpdir}/test.pid"
334
+ old_file = "#{pid_file}.oldbin"
335
+ ucfg = Tempfile.new('unicorn_test_config')
336
+ ucfg.syswrite("listen %(#@addr:#@port)\n")
337
+ ucfg.syswrite("pid %(#{pid_file})\n")
338
+ ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
339
+ pid = xfork do
340
+ redirect_test_io do
341
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
342
+ end
343
+ end
344
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
345
+ assert_equal String, results[0].class
346
+
347
+ wait_for_file(pid_file)
348
+ Process.waitpid(pid)
349
+ Process.kill(:USR2, File.read(pid_file).to_i)
350
+ wait_for_file(old_file)
351
+ wait_for_file(pid_file)
352
+ old_pid = File.read(old_file).to_i
353
+ Process.kill(:TERM, old_pid)
354
+ wait_for_death(old_pid)
355
+
356
+ ucfg.syswrite("timeout %(#{pid_file})\n") # introduce a bug
357
+ current_pid = File.read(pid_file).to_i
358
+ Process.kill(:USR2, current_pid)
359
+
360
+ # wait for pid_file to restore itself
361
+ tries = DEFAULT_TRIES
362
+ begin
363
+ while current_pid != File.read(pid_file).to_i
364
+ sleep(DEFAULT_RES) and (tries -= 1) > 0
365
+ end
366
+ rescue Errno::ENOENT
367
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
368
+ end
369
+ assert_equal current_pid, File.read(pid_file).to_i
370
+
371
+ tries = DEFAULT_TRIES
372
+ while File.exist?(old_file)
373
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
374
+ end
375
+ assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
376
+ port2 = unused_port(@addr)
377
+
378
+ # fix the bug
379
+ ucfg.sysseek(0)
380
+ ucfg.truncate(0)
381
+ ucfg.syswrite("listen %(#@addr:#@port)\n")
382
+ ucfg.syswrite("listen %(#@addr:#{port2})\n")
383
+ ucfg.syswrite("pid %(#{pid_file})\n")
384
+ assert_nothing_raised { Process.kill(:USR2, current_pid) }
385
+
386
+ wait_for_file(old_file)
387
+ wait_for_file(pid_file)
388
+ new_pid = File.read(pid_file).to_i
389
+ assert_not_equal current_pid, new_pid
390
+ assert_equal current_pid, File.read(old_file).to_i
391
+ results = retry_hit(["http://#{@addr}:#{@port}/",
392
+ "http://#{@addr}:#{port2}/"])
393
+ assert_equal String, results[0].class
394
+ assert_equal String, results[1].class
395
+
396
+ assert_nothing_raised do
397
+ Process.kill(:TERM, current_pid)
398
+ Process.kill(:TERM, new_pid)
399
+ end
400
+ end
401
+
402
+ def test_broken_reexec_ru
403
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
404
+ pid_file = "#{@tmpdir}/test.pid"
405
+ old_file = "#{pid_file}.oldbin"
406
+ ucfg = Tempfile.new('unicorn_test_config')
407
+ ucfg.syswrite("pid %(#{pid_file})\n")
408
+ ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
409
+ pid = xfork do
410
+ redirect_test_io do
411
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
412
+ end
413
+ end
414
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
415
+ assert_equal String, results[0].class
416
+
417
+ wait_for_file(pid_file)
418
+ Process.waitpid(pid)
419
+ Process.kill(:USR2, File.read(pid_file).to_i)
420
+ wait_for_file(old_file)
421
+ wait_for_file(pid_file)
422
+ old_pid = File.read(old_file).to_i
423
+ Process.kill(:TERM, old_pid)
424
+ wait_for_death(old_pid)
425
+
426
+ File.unlink("config.ru") # break reloading
427
+ current_pid = File.read(pid_file).to_i
428
+ Process.kill(:USR2, current_pid)
429
+
430
+ # wait for pid_file to restore itself
431
+ tries = DEFAULT_TRIES
432
+ begin
433
+ while current_pid != File.read(pid_file).to_i
434
+ sleep(DEFAULT_RES) and (tries -= 1) > 0
435
+ end
436
+ rescue Errno::ENOENT
437
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
438
+ end
439
+
440
+ tries = DEFAULT_TRIES
441
+ while File.exist?(old_file)
442
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
443
+ end
444
+ assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
445
+ assert_equal current_pid, File.read(pid_file).to_i
446
+
447
+ # fix the bug
448
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
449
+ assert_nothing_raised { Process.kill(:USR2, current_pid) }
450
+ wait_for_file(old_file)
451
+ wait_for_file(pid_file)
452
+ new_pid = File.read(pid_file).to_i
453
+ assert_not_equal current_pid, new_pid
454
+ assert_equal current_pid, File.read(old_file).to_i
455
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
456
+ assert_equal String, results[0].class
457
+
458
+ assert_nothing_raised do
459
+ Process.kill(:TERM, current_pid)
460
+ Process.kill(:TERM, new_pid)
461
+ end
462
+ end
463
+
464
+ def test_unicorn_config_listener_swap
465
+ port_cli = unused_port
466
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
467
+ ucfg = Tempfile.new('unicorn_test_config')
468
+ ucfg.syswrite("listen '#@addr:#@port'\n")
469
+ pid = xfork do
470
+ redirect_test_io do
471
+ exec($unicorn_bin, "-c#{ucfg.path}", "-l#@addr:#{port_cli}")
472
+ end
473
+ end
474
+ results = retry_hit(["http://#@addr:#{port_cli}/"])
475
+ assert_equal String, results[0].class
476
+ results = retry_hit(["http://#@addr:#@port/"])
477
+ assert_equal String, results[0].class
478
+
479
+ port2 = unused_port(@addr)
480
+ ucfg.sysseek(0)
481
+ ucfg.truncate(0)
482
+ ucfg.syswrite("listen '#@addr:#{port2}'\n")
483
+ Process.kill(:HUP, pid)
484
+
485
+ results = retry_hit(["http://#@addr:#{port2}/"])
486
+ assert_equal String, results[0].class
487
+ results = retry_hit(["http://#@addr:#{port_cli}/"])
488
+ assert_equal String, results[0].class
489
+ assert_nothing_raised do
490
+ reuse = TCPServer.new(@addr, @port)
491
+ reuse.close
492
+ end
493
+ assert_shutdown(pid)
494
+ end
495
+
496
+ def test_unicorn_config_listen_with_options
497
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
498
+ ucfg = Tempfile.new('unicorn_test_config')
499
+ ucfg.syswrite("listen '#{@addr}:#{@port}', :backlog => 512,\n")
500
+ ucfg.syswrite(" :rcvbuf => 4096,\n")
501
+ ucfg.syswrite(" :sndbuf => 4096\n")
502
+ pid = xfork do
503
+ redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") }
504
+ end
505
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
506
+ assert_equal String, results[0].class
507
+ assert_shutdown(pid)
508
+ end
509
+
510
+ def test_unicorn_config_per_worker_listen
511
+ port2 = unused_port
512
+ pid_spit = 'use Rack::ContentLength;' \
513
+ 'run proc { |e| [ 200, {"Content-Type"=>"text/plain"}, ["#$$\\n"] ] }'
514
+ File.open("config.ru", "wb") { |fp| fp.syswrite(pid_spit) }
515
+ tmp = Tempfile.new('test.socket')
516
+ File.unlink(tmp.path)
517
+ ucfg = Tempfile.new('unicorn_test_config')
518
+ ucfg.syswrite("listen '#@addr:#@port'\n")
519
+ ucfg.syswrite("after_fork { |s,w|\n")
520
+ ucfg.syswrite(" s.listen('#{tmp.path}', :backlog => 5, :sndbuf => 8192)\n")
521
+ ucfg.syswrite(" s.listen('#@addr:#{port2}', :rcvbuf => 8192)\n")
522
+ ucfg.syswrite("\n}\n")
523
+ pid = xfork do
524
+ redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") }
525
+ end
526
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
527
+ assert_equal String, results[0].class
528
+ worker_pid = results[0].to_i
529
+ assert_not_equal pid, worker_pid
530
+ s = UNIXSocket.new(tmp.path)
531
+ s.syswrite("GET / HTTP/1.0\r\n\r\n")
532
+ results = ''
533
+ loop { results << s.sysread(4096) } rescue nil
534
+ assert_nothing_raised { s.close }
535
+ assert_equal worker_pid, results.split(/\r\n/).last.to_i
536
+ results = hit(["http://#@addr:#{port2}/"])
537
+ assert_equal String, results[0].class
538
+ assert_equal worker_pid, results[0].to_i
539
+ assert_shutdown(pid)
540
+ end
541
+
542
+ def test_unicorn_config_listen_augments_cli
543
+ port2 = unused_port(@addr)
544
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
545
+ ucfg = Tempfile.new('unicorn_test_config')
546
+ ucfg.syswrite("listen '#{@addr}:#{@port}'\n")
547
+ pid = xfork do
548
+ redirect_test_io do
549
+ exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{port2}")
550
+ end
551
+ end
552
+ uris = [@port, port2].map { |i| "http://#{@addr}:#{i}/" }
553
+ results = retry_hit(uris)
554
+ assert_equal results.size, uris.size
555
+ assert_equal String, results[0].class
556
+ assert_equal String, results[1].class
557
+ assert_shutdown(pid)
558
+ end
559
+
560
+ def test_weird_config_settings
561
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
562
+ ucfg = Tempfile.new('unicorn_test_config')
563
+ ucfg.syswrite(HEAVY_CFG)
564
+ pid = xfork do
565
+ redirect_test_io do
566
+ exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{@port}")
567
+ end
568
+ end
569
+
570
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
571
+ assert_equal String, results[0].class
572
+ wait_master_ready(COMMON_TMP.path)
573
+ wait_workers_ready(COMMON_TMP.path, 4)
574
+ bf = File.readlines(COMMON_TMP.path).grep(/\bbefore_fork: worker=/)
575
+ assert_equal 4, bf.size
576
+ rotate = Tempfile.new('unicorn_rotate')
577
+ assert_nothing_raised do
578
+ File.rename(COMMON_TMP.path, rotate.path)
579
+ Process.kill(:USR1, pid)
580
+ end
581
+ wait_for_file(COMMON_TMP.path)
582
+ assert File.exist?(COMMON_TMP.path), "#{COMMON_TMP.path} exists"
583
+ # USR1 should've been passed to all workers
584
+ tries = DEFAULT_TRIES
585
+ log = File.readlines(rotate.path)
586
+ while (tries -= 1) > 0 &&
587
+ log.grep(/reopening logs\.\.\./).size < 5
588
+ sleep DEFAULT_RES
589
+ log = File.readlines(rotate.path)
590
+ end
591
+ assert_equal 5, log.grep(/reopening logs\.\.\./).size
592
+ assert_equal 0, log.grep(/done reopening logs/).size
593
+
594
+ tries = DEFAULT_TRIES
595
+ log = File.readlines(COMMON_TMP.path)
596
+ while (tries -= 1) > 0 && log.grep(/done reopening logs/).size < 5
597
+ sleep DEFAULT_RES
598
+ log = File.readlines(COMMON_TMP.path)
599
+ end
600
+ assert_equal 5, log.grep(/done reopening logs/).size
601
+ assert_equal 0, log.grep(/reopening logs\.\.\./).size
602
+ assert_nothing_raised { Process.kill(:TERM, pid) }
603
+ status = nil
604
+ assert_nothing_raised { pid, status = Process.waitpid2(pid) }
605
+ assert status.success?, "exited successfully"
606
+ end
607
+
608
+ def test_read_embedded_cli_switches
609
+ File.open("config.ru", "wb") do |fp|
610
+ fp.syswrite("#\\ -p #{@port} -o #{@addr}\n")
611
+ fp.syswrite(HI)
612
+ end
613
+ pid = fork { redirect_test_io { exec($unicorn_bin) } }
614
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
615
+ assert_equal String, results[0].class
616
+ assert_shutdown(pid)
617
+ end
618
+
619
+ def test_config_ru_alt_path
620
+ config_path = "#{@tmpdir}/foo.ru"
621
+ File.open(config_path, "wb") { |fp| fp.syswrite(HI) }
622
+ pid = fork do
623
+ redirect_test_io do
624
+ Dir.chdir("/")
625
+ exec($unicorn_bin, "-l#{@addr}:#{@port}", config_path)
626
+ end
627
+ end
628
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
629
+ assert_equal String, results[0].class
630
+ assert_shutdown(pid)
631
+ end
632
+
633
+ def test_load_module
634
+ libdir = "#{@tmpdir}/lib"
635
+ FileUtils.mkpath([ libdir ])
636
+ config_path = "#{libdir}/hello.rb"
637
+ File.open(config_path, "wb") { |fp| fp.syswrite(HELLO) }
638
+ pid = fork do
639
+ redirect_test_io do
640
+ Dir.chdir("/")
641
+ exec($unicorn_bin, "-l#{@addr}:#{@port}", config_path)
642
+ end
643
+ end
644
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
645
+ assert_equal String, results[0].class
646
+ assert_shutdown(pid)
647
+ end
648
+
649
+ def test_reexec
650
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
651
+ pid_file = "#{@tmpdir}/test.pid"
652
+ pid = fork do
653
+ redirect_test_io do
654
+ exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}")
655
+ end
656
+ end
657
+ reexec_basic_test(pid, pid_file)
658
+ end
659
+
660
+ def test_reexec_alt_config
661
+ config_file = "#{@tmpdir}/foo.ru"
662
+ File.open(config_file, "wb") { |fp| fp.syswrite(HI) }
663
+ pid_file = "#{@tmpdir}/test.pid"
664
+ pid = fork do
665
+ redirect_test_io do
666
+ exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}", config_file)
667
+ end
668
+ end
669
+ reexec_basic_test(pid, pid_file)
670
+ end
671
+
672
+ def test_socket_unlinked_restore
673
+ results = nil
674
+ sock = Tempfile.new('unicorn_test_sock')
675
+ sock_path = sock.path
676
+ @sockets << sock_path
677
+ sock.close!
678
+ ucfg = Tempfile.new('unicorn_test_config')
679
+ ucfg.syswrite("listen \"#{sock_path}\"\n")
680
+
681
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
682
+ pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") } }
683
+ wait_for_file(sock_path)
684
+ assert File.socket?(sock_path)
685
+ assert_nothing_raised do
686
+ sock = UNIXSocket.new(sock_path)
687
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
688
+ results = sock.sysread(4096)
689
+ end
690
+ assert_equal String, results.class
691
+ assert_nothing_raised do
692
+ File.unlink(sock_path)
693
+ Process.kill(:HUP, pid)
694
+ end
695
+ wait_for_file(sock_path)
696
+ assert File.socket?(sock_path)
697
+ assert_nothing_raised do
698
+ sock = UNIXSocket.new(sock_path)
699
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
700
+ results = sock.sysread(4096)
701
+ end
702
+ assert_equal String, results.class
703
+ end
704
+
705
+ def test_unicorn_config_file
706
+ pid_file = "#{@tmpdir}/test.pid"
707
+ sock = Tempfile.new('unicorn_test_sock')
708
+ sock_path = sock.path
709
+ sock.close!
710
+ @sockets << sock_path
711
+
712
+ log = Tempfile.new('unicorn_test_log')
713
+ ucfg = Tempfile.new('unicorn_test_config')
714
+ ucfg.syswrite("listen \"#{sock_path}\"\n")
715
+ ucfg.syswrite("pid \"#{pid_file}\"\n")
716
+ ucfg.syswrite("logger Logger.new('#{log.path}')\n")
717
+ ucfg.close
718
+
719
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
720
+ pid = xfork do
721
+ redirect_test_io do
722
+ exec($unicorn_bin, "-l#{@addr}:#{@port}",
723
+ "-P#{pid_file}", "-c#{ucfg.path}")
724
+ end
725
+ end
726
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
727
+ assert_equal String, results[0].class
728
+ wait_master_ready(log.path)
729
+ assert File.exist?(pid_file), "pid_file created"
730
+ assert_equal pid, File.read(pid_file).to_i
731
+ assert File.socket?(sock_path), "socket created"
732
+ assert_nothing_raised do
733
+ sock = UNIXSocket.new(sock_path)
734
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
735
+ results = sock.sysread(4096)
736
+ end
737
+ assert_equal String, results.class
738
+
739
+ # try reloading the config
740
+ sock = Tempfile.new('new_test_sock')
741
+ new_sock_path = sock.path
742
+ @sockets << new_sock_path
743
+ sock.close!
744
+ new_log = Tempfile.new('unicorn_test_log')
745
+ new_log.sync = true
746
+ assert_equal 0, new_log.size
747
+
748
+ assert_nothing_raised do
749
+ ucfg = File.open(ucfg.path, "wb")
750
+ ucfg.syswrite("listen \"#{sock_path}\"\n")
751
+ ucfg.syswrite("listen \"#{new_sock_path}\"\n")
752
+ ucfg.syswrite("pid \"#{pid_file}\"\n")
753
+ ucfg.syswrite("logger Logger.new('#{new_log.path}')\n")
754
+ ucfg.close
755
+ Process.kill(:HUP, pid)
756
+ end
757
+
758
+ wait_for_file(new_sock_path)
759
+ assert File.socket?(new_sock_path), "socket exists"
760
+ @sockets.each do |path|
761
+ assert_nothing_raised do
762
+ sock = UNIXSocket.new(path)
763
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
764
+ results = sock.sysread(4096)
765
+ end
766
+ assert_equal String, results.class
767
+ end
768
+
769
+ assert_not_equal 0, new_log.size
770
+ reexec_usr2_quit_test(pid, pid_file)
771
+ end
772
+
773
+ def test_daemonize_reexec
774
+ pid_file = "#{@tmpdir}/test.pid"
775
+ log = Tempfile.new('unicorn_test_log')
776
+ ucfg = Tempfile.new('unicorn_test_config')
777
+ ucfg.syswrite("pid \"#{pid_file}\"\n")
778
+ ucfg.syswrite("logger Logger.new('#{log.path}')\n")
779
+ ucfg.close
780
+
781
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
782
+ pid = xfork do
783
+ redirect_test_io do
784
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
785
+ end
786
+ end
787
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
788
+ assert_equal String, results[0].class
789
+ wait_for_file(pid_file)
790
+ new_pid = File.read(pid_file).to_i
791
+ assert_not_equal pid, new_pid
792
+ pid, status = Process.waitpid2(pid)
793
+ assert status.success?, "original process exited successfully"
794
+ assert_nothing_raised { Process.kill(0, new_pid) }
795
+ reexec_usr2_quit_test(new_pid, pid_file)
796
+ end
797
+
798
+ def test_daemonize_redirect_fail
799
+ pid_file = "#{@tmpdir}/test.pid"
800
+ ucfg = Tempfile.new('unicorn_test_config')
801
+ ucfg.syswrite("pid #{pid_file}\"\n")
802
+ err = Tempfile.new('stderr')
803
+ out = Tempfile.new('stdout ')
804
+
805
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
806
+ pid = xfork do
807
+ $stderr.reopen(err.path, "a")
808
+ $stdout.reopen(out.path, "a")
809
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
810
+ end
811
+ pid, status = Process.waitpid2(pid)
812
+ assert ! status.success?, "original process exited successfully"
813
+ sleep 1 # can't waitpid on a daemonized process :<
814
+ assert err.stat.size > 0
815
+ end
816
+
817
+ def test_reexec_fd_leak
818
+ unless RUBY_PLATFORM =~ /linux/ # Solaris may work, too, but I forget...
819
+ warn "FD leak test only works on Linux at the moment"
820
+ return
821
+ end
822
+ pid_file = "#{@tmpdir}/test.pid"
823
+ log = Tempfile.new('unicorn_test_log')
824
+ log.sync = true
825
+ ucfg = Tempfile.new('unicorn_test_config')
826
+ ucfg.syswrite("pid \"#{pid_file}\"\n")
827
+ ucfg.syswrite("logger Logger.new('#{log.path}')\n")
828
+ ucfg.syswrite("stderr_path '#{log.path}'\n")
829
+ ucfg.syswrite("stdout_path '#{log.path}'\n")
830
+ ucfg.close
831
+
832
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
833
+ pid = xfork do
834
+ redirect_test_io do
835
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
836
+ end
837
+ end
838
+
839
+ wait_master_ready(log.path)
840
+ wait_workers_ready(log.path, 1)
841
+ File.truncate(log.path, 0)
842
+ wait_for_file(pid_file)
843
+ orig_pid = pid = File.read(pid_file).to_i
844
+ orig_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
845
+ assert $?.success?
846
+ expect_size = orig_fds.size
847
+
848
+ assert_nothing_raised do
849
+ Process.kill(:USR2, pid)
850
+ wait_for_file("#{pid_file}.oldbin")
851
+ Process.kill(:TERM, pid)
852
+ end
853
+ wait_for_death(pid)
854
+
855
+ wait_master_ready(log.path)
856
+ wait_workers_ready(log.path, 1)
857
+ File.truncate(log.path, 0)
858
+ wait_for_file(pid_file)
859
+ pid = File.read(pid_file).to_i
860
+ assert_not_equal orig_pid, pid
861
+ curr_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
862
+ assert $?.success?
863
+
864
+ # we could've inherited descriptors the first time around
865
+ assert expect_size >= curr_fds.size, curr_fds.inspect
866
+ expect_size = curr_fds.size
867
+
868
+ assert_nothing_raised do
869
+ Process.kill(:USR2, pid)
870
+ wait_for_file("#{pid_file}.oldbin")
871
+ Process.kill(:TERM, pid)
872
+ end
873
+ wait_for_death(pid)
874
+
875
+ wait_master_ready(log.path)
876
+ wait_workers_ready(log.path, 1)
877
+ File.truncate(log.path, 0)
878
+ wait_for_file(pid_file)
879
+ pid = File.read(pid_file).to_i
880
+ curr_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
881
+ assert $?.success?
882
+ assert_equal expect_size, curr_fds.size, curr_fds.inspect
883
+
884
+ Process.kill(:TERM, pid)
885
+ wait_for_death(pid)
886
+ end
887
+
888
+ def hup_test_common(preload)
889
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI.gsub("HI", '#$$')) }
890
+ pid_file = Tempfile.new('pid')
891
+ ucfg = Tempfile.new('unicorn_test_config')
892
+ ucfg.syswrite("listen '#@addr:#@port'\n")
893
+ ucfg.syswrite("pid '#{pid_file.path}'\n")
894
+ ucfg.syswrite("preload_app true\n") if preload
895
+ ucfg.syswrite("stderr_path 'test_stderr.#$$.log'\n")
896
+ ucfg.syswrite("stdout_path 'test_stdout.#$$.log'\n")
897
+ pid = xfork {
898
+ redirect_test_io { exec($unicorn_bin, "-D", "-c", ucfg.path) }
899
+ }
900
+ _, status = Process.waitpid2(pid)
901
+ assert status.success?
902
+ wait_master_ready("test_stderr.#$$.log")
903
+ wait_workers_ready("test_stderr.#$$.log", 1)
904
+ uri = URI.parse("http://#@addr:#@port/")
905
+ pids = Tempfile.new('worker_pids')
906
+ r, w = IO.pipe
907
+ hitter = fork {
908
+ r.close
909
+ bodies = Hash.new(0)
910
+ at_exit { pids.syswrite(bodies.inspect) }
911
+ trap(:QUIT) { exit(0) }
912
+ nr = 0
913
+ loop {
914
+ rv = Net::HTTP.get(uri)
915
+ pid = rv.to_i
916
+ exit!(1) if pid <= 0
917
+ bodies[pid] += 1
918
+ nr += 1
919
+ if nr == 1
920
+ w.syswrite('1')
921
+ elsif bodies.size > 1
922
+ w.syswrite('2')
923
+ sleep
924
+ end
925
+ }
926
+ }
927
+ w.close
928
+ assert_equal '1', r.read(1)
929
+ daemon_pid = File.read(pid_file.path).to_i
930
+ assert daemon_pid > 0
931
+ Process.kill(:HUP, daemon_pid)
932
+ assert_equal '2', r.read(1)
933
+ assert_nothing_raised { Process.kill(:QUIT, hitter) }
934
+ _, hitter_status = Process.waitpid2(hitter)
935
+ assert(hitter_status.success?,
936
+ "invalid: #{hitter_status.inspect} #{File.read(pids.path)}" \
937
+ "#{File.read("test_stderr.#$$.log")}")
938
+ pids.sysseek(0)
939
+ pids = eval(pids.read)
940
+ assert_kind_of(Hash, pids)
941
+ assert_equal 2, pids.size
942
+ pids.keys.each { |x|
943
+ assert_kind_of(Integer, x)
944
+ assert x > 0
945
+ assert pids[x] > 0
946
+ }
947
+ assert_nothing_raised { Process.kill(:TERM, daemon_pid) }
948
+ wait_for_death(daemon_pid)
949
+ end
950
+
951
+ def test_preload_app_hup
952
+ hup_test_common(true)
953
+ end
954
+
955
+ def test_hup
956
+ hup_test_common(false)
957
+ end
958
+
959
+ def test_default_listen_hup_holds_listener
960
+ default_listen_lock do
961
+ res, pid_path = default_listen_setup
962
+ daemon_pid = File.read(pid_path).to_i
963
+ assert_nothing_raised { Process.kill(:HUP, daemon_pid) }
964
+ wait_workers_ready("test_stderr.#$$.log", 1)
965
+ res2 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
966
+ assert_match %r{\d+}, res2.first
967
+ assert res2.first != res.first
968
+ assert_nothing_raised { Process.kill(:TERM, daemon_pid) }
969
+ wait_for_death(daemon_pid)
970
+ end
971
+ end
972
+
973
+ def test_default_listen_upgrade_holds_listener
974
+ default_listen_lock do
975
+ res, pid_path = default_listen_setup
976
+ daemon_pid = File.read(pid_path).to_i
977
+ assert_nothing_raised {
978
+ Process.kill(:USR2, daemon_pid)
979
+ wait_for_file("#{pid_path}.oldbin")
980
+ wait_for_file(pid_path)
981
+ Process.kill(:TERM, daemon_pid)
982
+ wait_for_death(daemon_pid)
983
+ }
984
+ daemon_pid = File.read(pid_path).to_i
985
+ wait_workers_ready("test_stderr.#$$.log", 1)
986
+ File.truncate("test_stderr.#$$.log", 0)
987
+
988
+ res2 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
989
+ assert_match %r{\d+}, res2.first
990
+ assert res2.first != res.first
991
+
992
+ assert_nothing_raised { Process.kill(:HUP, daemon_pid) }
993
+ wait_workers_ready("test_stderr.#$$.log", 1)
994
+ File.truncate("test_stderr.#$$.log", 0)
995
+ res3 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
996
+ assert res2.first != res3.first
997
+
998
+ assert_nothing_raised { Process.kill(:TERM, daemon_pid) }
999
+ wait_for_death(daemon_pid)
1000
+ end
1001
+ end
1002
+
1003
+ def default_listen_setup
1004
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI.gsub("HI", '#$$')) }
1005
+ pid_path = (tmp = Tempfile.new('pid')).path
1006
+ tmp.close!
1007
+ ucfg = Tempfile.new('unicorn_test_config')
1008
+ ucfg.syswrite("pid '#{pid_path}'\n")
1009
+ ucfg.syswrite("stderr_path 'test_stderr.#$$.log'\n")
1010
+ ucfg.syswrite("stdout_path 'test_stdout.#$$.log'\n")
1011
+ pid = xfork {
1012
+ redirect_test_io { exec($unicorn_bin, "-D", "-c", ucfg.path) }
1013
+ }
1014
+ _, status = Process.waitpid2(pid)
1015
+ assert status.success?
1016
+ wait_master_ready("test_stderr.#$$.log")
1017
+ wait_workers_ready("test_stderr.#$$.log", 1)
1018
+ File.truncate("test_stderr.#$$.log", 0)
1019
+ res = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
1020
+ assert_match %r{\d+}, res.first
1021
+ [ res, pid_path ]
1022
+ end
1023
+
1024
+ # we need to flock() something to prevent these tests from running
1025
+ def default_listen_lock(&block)
1026
+ fp = File.open(FLOCK_PATH, "rb")
1027
+ begin
1028
+ fp.flock(File::LOCK_EX)
1029
+ begin
1030
+ TCPServer.new(Unicorn::Const::DEFAULT_HOST,
1031
+ Unicorn::Const::DEFAULT_PORT).close
1032
+ rescue Errno::EADDRINUSE, Errno::EACCES
1033
+ warn "can't bind to #{Unicorn::Const::DEFAULT_LISTEN}"
1034
+ return false
1035
+ end
1036
+
1037
+ # unused_port should never take this, but we may run an environment
1038
+ # where tests are being run against older unicorns...
1039
+ lock_path = "#{Dir::tmpdir}/unicorn_test." \
1040
+ "#{Unicorn::Const::DEFAULT_LISTEN}.lock"
1041
+ begin
1042
+ File.open(lock_path, File::WRONLY|File::CREAT|File::EXCL, 0600)
1043
+ yield
1044
+ rescue Errno::EEXIST
1045
+ lock_path = nil
1046
+ return false
1047
+ ensure
1048
+ File.unlink(lock_path) if lock_path
1049
+ end
1050
+ ensure
1051
+ fp.flock(File::LOCK_UN)
1052
+ end
1053
+ end
1054
+
1055
+ end if do_test