unicorn 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/test/aggregate.rb ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/ruby -n
2
+ BEGIN { $tests = $assertions = $failures = $errors = 0 }
3
+
4
+ $_ =~ /(\d+) tests, (\d+) assertions, (\d+) failures, (\d+) errors/ or next
5
+ $tests += $1.to_i
6
+ $assertions += $2.to_i
7
+ $failures += $3.to_i
8
+ $errors += $4.to_i
9
+
10
+ END {
11
+ printf("\n%d tests, %d assertions, %d failures, %d errors\n",
12
+ $tests, $assertions, $failures, $errors)
13
+ }
@@ -0,0 +1,11 @@
1
+ # Benchmark to compare Mongrel performance against
2
+ # previous Mongrel version (the one installed as a gem).
3
+ #
4
+ # Run with:
5
+ #
6
+ # ruby previous.rb [num of request]
7
+ #
8
+
9
+ require File.dirname(__FILE__) + '/utils'
10
+
11
+ benchmark "print", %w(current gem), 1000, [1, 10, 100]
@@ -0,0 +1,11 @@
1
+ #
2
+ # Simple benchmark to compare Mongrel performance against
3
+ # other webservers supported by Rack.
4
+ #
5
+
6
+ require File.dirname(__FILE__) + '/utils'
7
+
8
+ libs = %w(current gem WEBrick EMongrel Thin)
9
+ libs = ARGV if ARGV.any?
10
+
11
+ benchmark "print", libs, 1000, [1, 10, 100]
@@ -0,0 +1,82 @@
1
+
2
+ require 'rubygems'
3
+ require 'rack'
4
+ require 'rack/lobster'
5
+
6
+ def run(handler_name, n=1000, c=1)
7
+ port = 7000
8
+
9
+ server = fork do
10
+ [STDOUT, STDERR].each { |o| o.reopen "/dev/null" }
11
+
12
+ case handler_name
13
+ when 'EMongrel'
14
+ require 'swiftcore/evented_mongrel'
15
+ handler_name = 'Mongrel'
16
+
17
+ when 'Thin'
18
+ require 'thin'
19
+ hander_name = 'Thin'
20
+
21
+ when 'gem' # Load the current Mongrel gem
22
+ require 'mongrel'
23
+ handler_name = 'Mongrel'
24
+
25
+ when 'current' # Load the current Mongrel version under /lib
26
+ require File.dirname(__FILE__) + '/../lib/mongrel'
27
+ handler_name = 'Mongrel'
28
+
29
+ end
30
+
31
+ app = Rack::Lobster.new
32
+
33
+ handler = Rack::Handler.const_get(handler_name)
34
+ handler.run app, :Host => '0.0.0.0', :Port => port
35
+ end
36
+
37
+ sleep 2
38
+
39
+ out = `nice -n20 ab -c #{c} -n #{n} http://127.0.0.1:#{port}/ 2> /dev/null`
40
+
41
+ Process.kill('SIGKILL', server)
42
+ Process.wait
43
+
44
+ if requests = out.match(/^Requests.+?(\d+\.\d+)/)
45
+ requests[1].to_i
46
+ else
47
+ 0
48
+ end
49
+ end
50
+
51
+ def benchmark(type, servers, request, concurrency_levels)
52
+ send "#{type}_benchmark", servers, request, concurrency_levels
53
+ end
54
+
55
+ def graph_benchmark(servers, request, concurrency_levels)
56
+ require '/usr/local/lib/ruby/gems/1.8/gems/gruff-0.2.9/lib/gruff'
57
+ g = Gruff::Area.new
58
+ g.title = "Server benchmark"
59
+
60
+ servers.each do |server|
61
+ g.data(server, concurrency_levels.collect { |c| print '.'; run(server, request, c) })
62
+ end
63
+ puts
64
+
65
+ g.x_axis_label = 'Concurrency'
66
+ g.y_axis_label = 'Requests / sec'
67
+ g.labels = {}
68
+ concurrency_levels.each_with_index { |c, i| g.labels[i] = c.to_s }
69
+
70
+ g.write('bench.png')
71
+ `open bench.png`
72
+ end
73
+
74
+ def print_benchmark(servers, request, concurrency_levels)
75
+ puts 'server request concurrency req/s'
76
+ puts '=' * 42
77
+ concurrency_levels.each do |c|
78
+ servers.each do |server|
79
+ puts "#{server.ljust(8)} #{request} #{c.to_s.ljust(4)} #{run(server, request, c)}"
80
+ end
81
+ end
82
+ end
data/test/exec/README ADDED
@@ -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,570 @@
1
+ # Copyright (c) 2009 Eric Wong
2
+ STDIN.sync = STDOUT.sync = STDERR.sync = true
3
+ require 'test/test_helper'
4
+ require 'pathname'
5
+ require 'tempfile'
6
+ require 'fileutils'
7
+
8
+ do_test = true
9
+ DEFAULT_TRIES = 1000
10
+ DEFAULT_RES = 0.2
11
+
12
+ $unicorn_bin = ENV['UNICORN_TEST_BIN'] || "unicorn"
13
+ redirect_test_io do
14
+ do_test = system($unicorn_bin, '-v')
15
+ end
16
+
17
+ unless do_test
18
+ STDERR.puts "#{$unicorn_bin} not found in PATH=#{ENV['PATH']}, " \
19
+ "skipping this test"
20
+ end
21
+
22
+ begin
23
+ require 'rack'
24
+ rescue LoadError
25
+ STDERR.puts "Unable to load Rack, skipping this test"
26
+ do_test = false
27
+ end
28
+
29
+ class ExecTest < Test::Unit::TestCase
30
+ trap('QUIT', 'IGNORE')
31
+
32
+ HI = <<-EOS
33
+ use Rack::ContentLength
34
+ run proc { |env| [ 200, { 'Content-Type' => 'text/plain' }, "HI\\n" ] }
35
+ EOS
36
+
37
+ HELLO = <<-EOS
38
+ class Hello
39
+ def call(env)
40
+ [ 200, { 'Content-Type' => 'text/plain' }, "HI\\n" ]
41
+ end
42
+ end
43
+ EOS
44
+
45
+ COMMON_TMP = Tempfile.new('unicorn_tmp') unless defined?(COMMON_TMP)
46
+
47
+ HEAVY_CFG = <<-EOS
48
+ worker_processes 4
49
+ timeout 30
50
+ backlog 128
51
+ logger Logger.new('#{COMMON_TMP.path}')
52
+ before_fork do |server, worker_nr|
53
+ server.logger.info "before_fork: worker=\#{worker_nr}"
54
+ end
55
+ EOS
56
+
57
+ def setup
58
+ @pwd = Dir.pwd
59
+ @tmpfile = Tempfile.new('unicorn_exec_test')
60
+ @tmpdir = @tmpfile.path
61
+ @tmpfile.close!
62
+ Dir.mkdir(@tmpdir)
63
+ Dir.chdir(@tmpdir)
64
+ @addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
65
+ @port = unused_port(@addr)
66
+ @sockets = []
67
+ @start_pid = $$
68
+ end
69
+
70
+ def teardown
71
+ return if @start_pid != $$
72
+ Dir.chdir(@pwd)
73
+ FileUtils.rmtree(@tmpdir)
74
+ @sockets.each { |path| File.unlink(path) rescue nil }
75
+ loop do
76
+ Process.kill('-QUIT', 0)
77
+ begin
78
+ Process.waitpid(-1, Process::WNOHANG) or break
79
+ rescue Errno::ECHILD
80
+ break
81
+ end
82
+ end
83
+ end
84
+
85
+ def test_basic
86
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
87
+ pid = fork do
88
+ redirect_test_io { exec($unicorn_bin, "-l", "#{@addr}:#{@port}") }
89
+ end
90
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
91
+ assert_equal String, results[0].class
92
+ assert_shutdown(pid)
93
+ end
94
+
95
+ def test_help
96
+ redirect_test_io do
97
+ assert(system($unicorn_bin, "-h"), "help text returns true")
98
+ end
99
+ assert_equal 0, File.stat("test_stderr.#$$.log").size
100
+ assert_not_equal 0, File.stat("test_stdout.#$$.log").size
101
+ lines = File.readlines("test_stdout.#$$.log")
102
+
103
+ # Be considerate of the on-call technician working from their
104
+ # mobile phone or netbook on a slow connection :)
105
+ assert lines.size <= 24, "help height fits in an ANSI terminal window"
106
+ lines.each do |line|
107
+ assert line.size <= 80, "help width fits in an ANSI terminal window"
108
+ end
109
+ end
110
+
111
+ def test_broken_reexec_config
112
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
113
+ pid_file = "#{@tmpdir}/test.pid"
114
+ old_file = "#{pid_file}.oldbin"
115
+ ucfg = Tempfile.new('unicorn_test_config')
116
+ ucfg.syswrite("listeners %w(#{@addr}:#{@port})\n")
117
+ ucfg.syswrite("pid %(#{pid_file})\n")
118
+ ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
119
+ pid = xfork do
120
+ redirect_test_io do
121
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
122
+ end
123
+ end
124
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
125
+ assert_equal String, results[0].class
126
+
127
+ wait_for_file(pid_file)
128
+ Process.waitpid(pid)
129
+ Process.kill('USR2', File.read(pid_file).to_i)
130
+ wait_for_file(old_file)
131
+ wait_for_file(pid_file)
132
+ Process.kill('QUIT', File.read(old_file).to_i)
133
+
134
+ ucfg.syswrite("timeout %(#{pid_file})\n") # introduce a bug
135
+ current_pid = File.read(pid_file).to_i
136
+ Process.kill('USR2', current_pid)
137
+
138
+ # wait for pid_file to restore itself
139
+ tries = DEFAULT_TRIES
140
+ begin
141
+ while current_pid != File.read(pid_file).to_i
142
+ sleep(DEFAULT_RES) and (tries -= 1) > 0
143
+ end
144
+ rescue Errno::ENOENT
145
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
146
+ end
147
+ assert_equal current_pid, File.read(pid_file).to_i
148
+
149
+ tries = DEFAULT_TRIES
150
+ while File.exist?(old_file)
151
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
152
+ end
153
+ assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
154
+ port2 = unused_port(@addr)
155
+
156
+ # fix the bug
157
+ ucfg.sysseek(0)
158
+ ucfg.truncate(0)
159
+ ucfg.syswrite("listeners %w(#{@addr}:#{@port} #{@addr}:#{port2})\n")
160
+ ucfg.syswrite("pid %(#{pid_file})\n")
161
+ Process.kill('USR2', current_pid)
162
+ wait_for_file(old_file)
163
+ wait_for_file(pid_file)
164
+ new_pid = File.read(pid_file).to_i
165
+ assert_not_equal current_pid, new_pid
166
+ assert_equal current_pid, File.read(old_file).to_i
167
+ results = retry_hit(["http://#{@addr}:#{@port}/",
168
+ "http://#{@addr}:#{port2}/"])
169
+ assert_equal String, results[0].class
170
+ assert_equal String, results[1].class
171
+
172
+ assert_nothing_raised do
173
+ Process.kill('QUIT', current_pid)
174
+ Process.kill('QUIT', new_pid)
175
+ end
176
+ end
177
+
178
+ def test_broken_reexec_ru
179
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
180
+ pid_file = "#{@tmpdir}/test.pid"
181
+ old_file = "#{pid_file}.oldbin"
182
+ ucfg = Tempfile.new('unicorn_test_config')
183
+ ucfg.syswrite("pid %(#{pid_file})\n")
184
+ ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
185
+ pid = xfork do
186
+ redirect_test_io do
187
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
188
+ end
189
+ end
190
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
191
+ assert_equal String, results[0].class
192
+
193
+ wait_for_file(pid_file)
194
+ Process.waitpid(pid)
195
+ Process.kill('USR2', File.read(pid_file).to_i)
196
+ wait_for_file(old_file)
197
+ wait_for_file(pid_file)
198
+ Process.kill('QUIT', File.read(old_file).to_i)
199
+
200
+ File.unlink("config.ru") # break reloading
201
+ current_pid = File.read(pid_file).to_i
202
+ Process.kill('USR2', current_pid)
203
+
204
+ # wait for pid_file to restore itself
205
+ tries = DEFAULT_TRIES
206
+ begin
207
+ while current_pid != File.read(pid_file).to_i
208
+ sleep(DEFAULT_RES) and (tries -= 1) > 0
209
+ end
210
+ rescue Errno::ENOENT
211
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
212
+ end
213
+ assert_equal current_pid, File.read(pid_file).to_i
214
+
215
+ tries = DEFAULT_TRIES
216
+ while File.exist?(old_file)
217
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
218
+ end
219
+ assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
220
+
221
+ # fix the bug
222
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
223
+ Process.kill('USR2', current_pid)
224
+ wait_for_file(old_file)
225
+ wait_for_file(pid_file)
226
+ new_pid = File.read(pid_file).to_i
227
+ assert_not_equal current_pid, new_pid
228
+ assert_equal current_pid, File.read(old_file).to_i
229
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
230
+ assert_equal String, results[0].class
231
+
232
+ assert_nothing_raised do
233
+ Process.kill('QUIT', current_pid)
234
+ Process.kill('QUIT', new_pid)
235
+ end
236
+ end
237
+
238
+ def test_unicorn_config_listeners_overrides_cli
239
+ port2 = unused_port(@addr)
240
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
241
+ # listeners = [ ... ] => should _override_ command-line options
242
+ ucfg = Tempfile.new('unicorn_test_config')
243
+ ucfg.syswrite("listeners %w(#{@addr}:#{@port})\n")
244
+ pid = xfork do
245
+ redirect_test_io do
246
+ exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{port2}")
247
+ end
248
+ end
249
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
250
+ assert_raises(Errno::ECONNREFUSED) { TCPSocket.new(@addr, port2) }
251
+ assert_equal String, results[0].class
252
+ assert_shutdown(pid)
253
+ end
254
+
255
+ def test_unicorn_config_listen_augments_cli
256
+ port2 = unused_port(@addr)
257
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
258
+ ucfg = Tempfile.new('unicorn_test_config')
259
+ ucfg.syswrite("listen '#{@addr}:#{@port}'\n")
260
+ pid = xfork do
261
+ redirect_test_io do
262
+ exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{port2}")
263
+ end
264
+ end
265
+ uris = [@port, port2].map { |i| "http://#{@addr}:#{i}/" }
266
+ results = retry_hit(uris)
267
+ assert_equal results.size, uris.size
268
+ assert_equal String, results[0].class
269
+ assert_equal String, results[1].class
270
+ assert_shutdown(pid)
271
+ end
272
+
273
+ def test_weird_config_settings
274
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
275
+ ucfg = Tempfile.new('unicorn_test_config')
276
+ ucfg.syswrite(HEAVY_CFG)
277
+ pid = xfork do
278
+ redirect_test_io do
279
+ exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{@port}")
280
+ end
281
+ end
282
+
283
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
284
+ assert_equal String, results[0].class
285
+ wait_master_ready(COMMON_TMP.path)
286
+ bf = File.readlines(COMMON_TMP.path).grep(/\bbefore_fork: worker=/)
287
+ assert_equal 4, bf.size
288
+ rotate = Tempfile.new('unicorn_rotate')
289
+ assert_nothing_raised do
290
+ File.rename(COMMON_TMP.path, rotate.path)
291
+ Process.kill('USR1', pid)
292
+ end
293
+ wait_for_file(COMMON_TMP.path)
294
+ assert File.exist?(COMMON_TMP.path), "#{COMMON_TMP.path} exists"
295
+ # USR1 should've been passed to all workers
296
+ tries = DEFAULT_TRIES
297
+ log = File.readlines(rotate.path)
298
+ while (tries -= 1) > 0 && log.grep(/rotating logs\.\.\./).size < 4
299
+ sleep DEFAULT_RES
300
+ log = File.readlines(rotate.path)
301
+ end
302
+ assert_equal 4, log.grep(/rotating logs\.\.\./).size
303
+ assert_equal 0, log.grep(/done rotating logs/).size
304
+
305
+ tries = DEFAULT_TRIES
306
+ log = File.readlines(COMMON_TMP.path)
307
+ while (tries -= 1) > 0 && log.grep(/done rotating logs/).size < 4
308
+ sleep DEFAULT_RES
309
+ log = File.readlines(COMMON_TMP.path)
310
+ end
311
+ assert_equal 4, log.grep(/done rotating logs/).size
312
+ assert_equal 0, log.grep(/rotating logs\.\.\./).size
313
+ assert_nothing_raised { Process.kill('QUIT', pid) }
314
+ status = nil
315
+ assert_nothing_raised { pid, status = Process.waitpid2(pid) }
316
+ assert status.success?, "exited successfully"
317
+ end
318
+
319
+ def test_read_embedded_cli_switches
320
+ File.open("config.ru", "wb") do |fp|
321
+ fp.syswrite("#\\ -p #{@port} -o #{@addr}\n")
322
+ fp.syswrite(HI)
323
+ end
324
+ pid = fork { redirect_test_io { exec($unicorn_bin) } }
325
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
326
+ assert_equal String, results[0].class
327
+ assert_shutdown(pid)
328
+ end
329
+
330
+ def test_config_ru_alt_path
331
+ config_path = "#{@tmpdir}/foo.ru"
332
+ File.open(config_path, "wb") { |fp| fp.syswrite(HI) }
333
+ pid = fork do
334
+ redirect_test_io do
335
+ Dir.chdir("/")
336
+ exec($unicorn_bin, "-l#{@addr}:#{@port}", config_path)
337
+ end
338
+ end
339
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
340
+ assert_equal String, results[0].class
341
+ assert_shutdown(pid)
342
+ end
343
+
344
+ def test_load_module
345
+ libdir = "#{@tmpdir}/lib"
346
+ FileUtils.mkpath([ libdir ])
347
+ config_path = "#{libdir}/hello.rb"
348
+ File.open(config_path, "wb") { |fp| fp.syswrite(HELLO) }
349
+ pid = fork do
350
+ redirect_test_io do
351
+ Dir.chdir("/")
352
+ exec($unicorn_bin, "-l#{@addr}:#{@port}", config_path)
353
+ end
354
+ end
355
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
356
+ assert_equal String, results[0].class
357
+ assert_shutdown(pid)
358
+ end
359
+
360
+ def test_reexec
361
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
362
+ pid_file = "#{@tmpdir}/test.pid"
363
+ pid = fork do
364
+ redirect_test_io do
365
+ exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}")
366
+ end
367
+ end
368
+ reexec_basic_test(pid, pid_file)
369
+ end
370
+
371
+ def test_reexec_alt_config
372
+ config_file = "#{@tmpdir}/foo.ru"
373
+ File.open(config_file, "wb") { |fp| fp.syswrite(HI) }
374
+ pid_file = "#{@tmpdir}/test.pid"
375
+ pid = fork do
376
+ redirect_test_io do
377
+ exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}", config_file)
378
+ end
379
+ end
380
+ reexec_basic_test(pid, pid_file)
381
+ end
382
+
383
+ def test_unicorn_config_file
384
+ pid_file = "#{@tmpdir}/test.pid"
385
+ sock = Tempfile.new('unicorn_test_sock')
386
+ sock_path = sock.path
387
+ sock.close!
388
+ @sockets << sock_path
389
+
390
+ log = Tempfile.new('unicorn_test_log')
391
+ ucfg = Tempfile.new('unicorn_test_config')
392
+ ucfg.syswrite("listen \"#{sock_path}\"\n")
393
+ ucfg.syswrite("pid \"#{pid_file}\"\n")
394
+ ucfg.syswrite("logger Logger.new('#{log.path}')\n")
395
+ ucfg.close
396
+
397
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
398
+ pid = xfork do
399
+ redirect_test_io do
400
+ exec($unicorn_bin, "-l#{@addr}:#{@port}",
401
+ "-P#{pid_file}", "-c#{ucfg.path}")
402
+ end
403
+ end
404
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
405
+ assert_equal String, results[0].class
406
+ wait_master_ready(log.path)
407
+ assert File.exist?(pid_file), "pid_file created"
408
+ assert_equal pid, File.read(pid_file).to_i
409
+ assert File.socket?(sock_path), "socket created"
410
+ assert_nothing_raised do
411
+ sock = UNIXSocket.new(sock_path)
412
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
413
+ results = sock.sysread(4096)
414
+ end
415
+ assert_equal String, results.class
416
+
417
+ # try reloading the config
418
+ sock = Tempfile.new('unicorn_test_sock')
419
+ new_sock_path = sock.path
420
+ @sockets << new_sock_path
421
+ sock.close!
422
+ new_log = Tempfile.new('unicorn_test_log')
423
+ new_log.sync = true
424
+ assert_equal 0, new_log.size
425
+
426
+ assert_nothing_raised do
427
+ ucfg = File.open(ucfg.path, "wb")
428
+ ucfg.syswrite("listen \"#{new_sock_path}\"\n")
429
+ ucfg.syswrite("pid \"#{pid_file}\"\n")
430
+ ucfg.syswrite("logger Logger.new('#{new_log.path}')\n")
431
+ ucfg.close
432
+ Process.kill('HUP', pid)
433
+ end
434
+
435
+ wait_for_file(new_sock_path)
436
+ assert File.socket?(new_sock_path), "socket exists"
437
+ @sockets.each do |path|
438
+ assert_nothing_raised do
439
+ sock = UNIXSocket.new(path)
440
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
441
+ results = sock.sysread(4096)
442
+ end
443
+ assert_equal String, results.class
444
+ end
445
+
446
+ assert_not_equal 0, new_log.size
447
+ reexec_usr2_quit_test(pid, pid_file)
448
+ end
449
+
450
+ def test_daemonize_reexec
451
+ pid_file = "#{@tmpdir}/test.pid"
452
+ log = Tempfile.new('unicorn_test_log')
453
+ ucfg = Tempfile.new('unicorn_test_config')
454
+ ucfg.syswrite("pid \"#{pid_file}\"\n")
455
+ ucfg.syswrite("logger Logger.new('#{log.path}')\n")
456
+ ucfg.close
457
+
458
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
459
+ pid = xfork do
460
+ redirect_test_io do
461
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
462
+ end
463
+ end
464
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
465
+ assert_equal String, results[0].class
466
+ wait_for_file(pid_file)
467
+ new_pid = File.read(pid_file).to_i
468
+ assert_not_equal pid, new_pid
469
+ pid, status = Process.waitpid2(pid)
470
+ assert status.success?, "original process exited successfully"
471
+ assert_nothing_raised { Process.kill(0, new_pid) }
472
+ reexec_usr2_quit_test(new_pid, pid_file)
473
+ end
474
+
475
+ private
476
+
477
+ # sometimes the server may not come up right away
478
+ def retry_hit(uris = [])
479
+ tries = DEFAULT_TRIES
480
+ begin
481
+ hit(uris)
482
+ rescue Errno::ECONNREFUSED => err
483
+ if (tries -= 1) > 0
484
+ sleep DEFAULT_RES
485
+ retry
486
+ end
487
+ raise err
488
+ end
489
+ end
490
+
491
+ def assert_shutdown(pid)
492
+ wait_master_ready("#{@tmpdir}/test_stderr.#{pid}.log")
493
+ assert_nothing_raised { Process.kill('QUIT', pid) }
494
+ status = nil
495
+ assert_nothing_raised { pid, status = Process.waitpid2(pid) }
496
+ assert status.success?, "exited successfully"
497
+ end
498
+
499
+ def wait_master_ready(master_log)
500
+ tries = DEFAULT_TRIES
501
+ while (tries -= 1) > 0
502
+ begin
503
+ File.readlines(master_log).grep(/master process ready/)[0] and return
504
+ rescue Errno::ENOENT
505
+ end
506
+ sleep DEFAULT_RES
507
+ end
508
+ raise "master process never became ready"
509
+ end
510
+
511
+ def reexec_usr2_quit_test(pid, pid_file)
512
+ assert File.exist?(pid_file), "pid file OK"
513
+ assert ! File.exist?("#{pid_file}.oldbin"), "oldbin pid file"
514
+ assert_nothing_raised { Process.kill('USR2', pid) }
515
+ assert_nothing_raised { retry_hit(["http://#{@addr}:#{@port}/"]) }
516
+ wait_for_file("#{pid_file}.oldbin")
517
+ wait_for_file(pid_file)
518
+
519
+ # kill old master process
520
+ assert_not_equal pid, File.read(pid_file).to_i
521
+ assert_equal pid, File.read("#{pid_file}.oldbin").to_i
522
+ assert_nothing_raised { Process.kill('QUIT', pid) }
523
+ assert_not_equal pid, File.read(pid_file).to_i
524
+ assert_nothing_raised { retry_hit(["http://#{@addr}:#{@port}/"]) }
525
+ wait_for_file(pid_file)
526
+ assert_nothing_raised { retry_hit(["http://#{@addr}:#{@port}/"]) }
527
+ assert_nothing_raised { Process.kill('QUIT', File.read(pid_file).to_i) }
528
+ end
529
+
530
+ def reexec_basic_test(pid, pid_file)
531
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
532
+ assert_equal String, results[0].class
533
+ assert_nothing_raised { Process.kill(0, pid) }
534
+ master_log = "#{@tmpdir}/test_stderr.#{pid}.log"
535
+ wait_master_ready(master_log)
536
+ File.truncate(master_log, 0)
537
+ nr = 50
538
+ kill_point = 2
539
+ assert_nothing_raised do
540
+ nr.times do |i|
541
+ hit(["http://#{@addr}:#{@port}/#{i}"])
542
+ i == kill_point and Process.kill('HUP', pid)
543
+ end
544
+ end
545
+ wait_master_ready(master_log)
546
+ assert File.exist?(pid_file), "pid=#{pid_file} exists"
547
+ new_pid = File.read(pid_file).to_i
548
+ assert_not_equal pid, new_pid
549
+ assert_nothing_raised { Process.kill(0, new_pid) }
550
+ assert_nothing_raised { Process.kill('QUIT', new_pid) }
551
+ end
552
+
553
+ def wait_for_file(path)
554
+ tries = DEFAULT_TRIES
555
+ while (tries -= 1) > 0 && ! File.exist?(path)
556
+ sleep DEFAULT_RES
557
+ end
558
+ assert File.exist?(path), "path=#{path} exists"
559
+ end
560
+
561
+ def xfork(&block)
562
+ fork do
563
+ ObjectSpace.each_object(Tempfile) do |tmp|
564
+ ObjectSpace.undefine_finalizer(tmp)
565
+ end
566
+ yield
567
+ end
568
+ end
569
+
570
+ end if do_test