unicorn 0.4.2 → 0.5.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.
@@ -13,9 +13,15 @@ class Unicorn::App::OldRails
13
13
 
14
14
  def call(env)
15
15
  cgi = Unicorn::CGIWrapper.new(env)
16
- Dispatcher.dispatch(cgi,
17
- ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS,
18
- cgi.body)
16
+ begin
17
+ Dispatcher.dispatch(cgi,
18
+ ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS,
19
+ cgi.body)
20
+ rescue Object => e
21
+ err = env['rack.errors']
22
+ out.write("#{e} #{e.message}\n")
23
+ e.backtrace.each { |line| out.write("#{line}\n") }
24
+ end
19
25
  cgi.out # finalize the response
20
26
  cgi.rack_response
21
27
  end
@@ -71,11 +71,9 @@ class Unicorn::CGIWrapper < ::CGI
71
71
 
72
72
  # Capitalized "Status:", with human-readable status code (e.g. "200 OK")
73
73
  parseable_status = @head.delete(Status)
74
- unless @status
75
- @status ||= parseable_status.split(/ /)[0].to_i rescue 404
76
- end
74
+ @status ||= parseable_status.split(/ /)[0].to_i rescue 500
77
75
 
78
- [ @status, @head, [ @body.string ] ]
76
+ [ @status || 500, @head, [ @body.string ] ]
79
77
  end
80
78
 
81
79
  # The header is typically called to send back the header. In our case we
@@ -12,8 +12,8 @@ module Unicorn
12
12
  # listen '0.0.0.0:9292'
13
13
  # timeout 10
14
14
  # pid "/tmp/my_app.pid"
15
- # after_fork do |server,worker_nr|
16
- # server.listen("127.0.0.1:#{9293 + worker_nr}") rescue nil
15
+ # after_fork do |server,worker|
16
+ # server.listen("127.0.0.1:#{9293 + worker.nr}") rescue nil
17
17
  # end
18
18
  class Configurator
19
19
  # The default logger writes its output to $stderr
@@ -25,18 +25,18 @@ module Unicorn
25
25
  :listeners => [],
26
26
  :logger => DEFAULT_LOGGER,
27
27
  :worker_processes => 1,
28
- :after_fork => lambda { |server, worker_nr|
29
- server.logger.info("worker=#{worker_nr} spawned pid=#{$$}")
28
+ :after_fork => lambda { |server, worker|
29
+ server.logger.info("worker=#{worker.nr} spawned pid=#{$$}")
30
30
 
31
31
  # per-process listener ports for debugging/admin:
32
32
  # "rescue nil" statement is needed because USR2 will
33
33
  # cause the master process to reexecute itself and the
34
34
  # per-worker ports can be taken, necessitating another
35
35
  # HUP after QUIT-ing the original master:
36
- # server.listen("127.0.0.1:#{8081 + worker_nr}") rescue nil
36
+ # server.listen("127.0.0.1:#{8081 + worker.nr}") rescue nil
37
37
  },
38
- :before_fork => lambda { |server, worker_nr|
39
- server.logger.info("worker=#{worker_nr} spawning...")
38
+ :before_fork => lambda { |server, worker|
39
+ server.logger.info("worker=#{worker.nr} spawning...")
40
40
  },
41
41
  :before_exec => lambda { |server|
42
42
  server.logger.info("forked child re-executing...")
@@ -97,23 +97,37 @@ module Unicorn
97
97
  # the worker after forking. The following is an example hook which adds
98
98
  # a per-process listener to every worker:
99
99
  #
100
- # after_fork do |server,worker_nr|
100
+ # after_fork do |server,worker|
101
101
  # # per-process listener ports for debugging/admin:
102
102
  # # "rescue nil" statement is needed because USR2 will
103
103
  # # cause the master process to reexecute itself and the
104
104
  # # per-worker ports can be taken, necessitating another
105
105
  # # HUP after QUIT-ing the original master:
106
- # server.listen("127.0.0.1:#{9293 + worker_nr}") rescue nil
106
+ # server.listen("127.0.0.1:#{9293 + worker.nr}") rescue nil
107
+ #
108
+ # # drop permissions to "www-data" in the worker
109
+ # # generally there's no reason to start Unicorn as a priviledged user
110
+ # # as it is not recommended to expose Unicorn to public clients.
111
+ # uid, gid = Process.euid, Process.egid
112
+ # user, group = 'www-data', 'www-data'
113
+ # target_uid = Etc.getpwnam(user).uid
114
+ # target_gid = Etc.getgrnam(group).gid
115
+ # worker.tempfile.chown(target_uid, target_gid)
116
+ # if uid != target_uid || gid != target_gid
117
+ # Process.initgroups(user, target_gid)
118
+ # Process::GID.change_privilege(target_gid)
119
+ # Process::UID.change_privilege(target_uid)
120
+ # end
107
121
  # end
108
- def after_fork(&block)
109
- set_hook(:after_fork, block)
122
+ def after_fork(*args, &block)
123
+ set_hook(:after_fork, block_given? ? block : args[0])
110
124
  end
111
125
 
112
126
  # sets before_fork got be a given Proc object. This Proc
113
127
  # object will be called by the master process before forking
114
128
  # each worker.
115
- def before_fork(&block)
116
- set_hook(:before_fork, block)
129
+ def before_fork(*args, &block)
130
+ set_hook(:before_fork, block_given? ? block : args[0])
117
131
  end
118
132
 
119
133
  # sets the before_exec hook to a given Proc object. This
@@ -122,8 +136,8 @@ module Unicorn
122
136
  # for freeing certain OS resources that you do NOT wish to
123
137
  # share with the reexeced child process.
124
138
  # There is no corresponding after_exec hook (for obvious reasons).
125
- def before_exec(&block)
126
- set_hook(:before_exec, block, 1)
139
+ def before_exec(*args, &block)
140
+ set_hook(:before_exec, block_given? ? block : args[0], 1)
127
141
  end
128
142
 
129
143
  # sets the timeout of worker processes to +seconds+. Workers
@@ -192,10 +206,6 @@ module Unicorn
192
206
  # specified.
193
207
  #
194
208
  # Defaults: operating system defaults
195
- #
196
- # Due to limitations of the operating system, options specified here
197
- # cannot affect existing listener sockets in any way, sockets must be
198
- # completely closed and rebound.
199
209
  def listen(address, opt = { :backlog => 1024 })
200
210
  address = expand_addr(address)
201
211
  if String === address
data/lib/unicorn/const.rb CHANGED
@@ -58,7 +58,7 @@ module Unicorn
58
58
  REQUEST_URI='REQUEST_URI'.freeze
59
59
  REQUEST_PATH='REQUEST_PATH'.freeze
60
60
 
61
- UNICORN_VERSION="0.4.2".freeze
61
+ UNICORN_VERSION="0.5.0".freeze
62
62
 
63
63
  UNICORN_TMP_BASE="unicorn".freeze
64
64
 
@@ -19,7 +19,6 @@ module Unicorn
19
19
  "rack.multiprocess" => true,
20
20
  "rack.multithread" => false,
21
21
  "rack.run_once" => false,
22
- "rack.url_scheme" => "http",
23
22
  "rack.version" => [0, 1],
24
23
  "SCRIPT_NAME" => "",
25
24
 
@@ -1,29 +1,4 @@
1
1
  require 'socket'
2
- require 'io/nonblock'
3
-
4
- # non-portable Socket code goes here:
5
- class Socket
6
- module Constants
7
- # configure platform-specific options (only tested on Linux 2.6 so far)
8
- case RUBY_PLATFORM
9
- when /linux/
10
- # from /usr/include/linux/tcp.h
11
- TCP_DEFER_ACCEPT = 9 unless defined?(TCP_DEFER_ACCEPT)
12
- TCP_CORK = 3 unless defined?(TCP_CORK)
13
- when /freebsd(([1-4]\..{1,2})|5\.[0-4])/
14
- when /freebsd/
15
- # Use the HTTP accept filter if available.
16
- # The struct made by pack() is defined in /usr/include/sys/socket.h
17
- # as accept_filter_arg
18
- unless `/sbin/sysctl -nq net.inet.accf.http`.empty?
19
- unless defined?(SO_ACCEPTFILTER_HTTPREADY)
20
- SO_ACCEPTFILTER_HTTPREADY = ['httpready',nil].pack('a16a240').freeze
21
- end
22
-
23
- end
24
- end
25
- end
26
- end
27
2
 
28
3
  class UNIXSocket
29
4
  UNICORN_PEERADDR = '127.0.0.1'.freeze
@@ -42,19 +17,15 @@ module Unicorn
42
17
  module SocketHelper
43
18
  include Socket::Constants
44
19
 
45
- def set_client_sockopt(sock)
46
- sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1) if defined?(TCP_NODELAY)
47
- sock.setsockopt(SOL_TCP, TCP_CORK, 1) if defined?(TCP_CORK)
48
- end
49
-
50
- def set_server_sockopt(sock)
51
- if defined?(TCP_DEFER_ACCEPT)
52
- sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, 1) rescue nil
53
- end
54
- if defined?(SO_ACCEPTFILTER_HTTPREADY)
55
- sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER,
56
- SO_ACCEPTFILTER_HTTPREADY) rescue nil
20
+ def set_server_sockopt(sock, opt)
21
+ opt ||= {}
22
+ if opt[:rcvbuf] || opt[:sndbuf]
23
+ log_buffer_sizes(sock, "before: ")
24
+ sock.setsockopt(SOL_SOCKET, SO_RCVBUF, opt[:rcvbuf]) if opt[:rcvbuf]
25
+ sock.setsockopt(SOL_SOCKET, SO_SNDBUF, opt[:sndbuf]) if opt[:sndbuf]
26
+ log_buffer_sizes(sock, " after: ")
57
27
  end
28
+ sock.listen(opt[:backlog] || 1024)
58
29
  end
59
30
 
60
31
  def log_buffer_sizes(sock, pfx = '')
@@ -70,7 +41,7 @@ module Unicorn
70
41
  def bind_listen(address = '0.0.0.0:8080', opt = { :backlog => 1024 })
71
42
  return address unless String === address
72
43
 
73
- domain, bind_addr = if address[0..0] == "/"
44
+ sock = if address[0..0] == "/"
74
45
  if File.exist?(address)
75
46
  if File.socket?(address)
76
47
  if self.respond_to?(:logger)
@@ -82,29 +53,18 @@ module Unicorn
82
53
  "socket=#{address} specified but it is not a socket!"
83
54
  end
84
55
  end
85
- [ AF_UNIX, Socket.pack_sockaddr_un(address) ]
56
+ old_umask = File.umask(0)
57
+ begin
58
+ UNIXServer.new(address)
59
+ ensure
60
+ File.umask(old_umask)
61
+ end
86
62
  elsif address =~ /^(\d+\.\d+\.\d+\.\d+):(\d+)$/
87
- [ AF_INET, Socket.pack_sockaddr_in($2.to_i, $1) ]
63
+ TCPServer.new($1, $2.to_i)
88
64
  else
89
65
  raise ArgumentError, "Don't know how to bind: #{address}"
90
66
  end
91
-
92
- sock = Socket.new(domain, SOCK_STREAM, 0)
93
- sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) if defined?(SO_REUSEADDR)
94
- begin
95
- sock.bind(bind_addr)
96
- rescue Errno::EADDRINUSE
97
- sock.close rescue nil
98
- return nil
99
- end
100
- if opt[:rcvbuf] || opt[:sndbuf]
101
- log_buffer_sizes(sock, "before: ")
102
- sock.setsockopt(SOL_SOCKET, SO_RCVBUF, opt[:rcvbuf]) if opt[:rcvbuf]
103
- sock.setsockopt(SOL_SOCKET, SO_SNDBUF, opt[:sndbuf]) if opt[:sndbuf]
104
- log_buffer_sizes(sock, " after: ")
105
- end
106
- sock.listen(opt[:backlog] || 1024)
107
- set_server_sockopt(sock) if domain == AF_INET
67
+ set_server_sockopt(sock, opt)
108
68
  sock
109
69
  end
110
70
 
data/local.mk.sample CHANGED
@@ -33,6 +33,7 @@ publish_doc:
33
33
  $(MAKE) doc
34
34
  $(MAKE) doc_gz
35
35
  rsync -av --delete doc/ dcvr:/srv/unicorn/
36
+ git ls-files | xargs touch
36
37
 
37
38
  # Create gzip variants of the same timestamp as the original so nginx
38
39
  # "gzip_static on" can serve the gzipped versions directly.
@@ -39,8 +39,8 @@ end
39
39
  worker_processes 4
40
40
  timeout 30
41
41
  logger Logger.new('#{COMMON_TMP.path}')
42
- before_fork do |server, worker_nr|
43
- server.logger.info "before_fork: worker=\#{worker_nr}"
42
+ before_fork do |server, worker|
43
+ server.logger.info "before_fork: worker=\#{worker.nr}"
44
44
  end
45
45
  EOS
46
46
 
@@ -231,6 +231,38 @@ end
231
231
  end
232
232
  end
233
233
 
234
+ def test_unicorn_config_listener_swap
235
+ port_cli = unused_port
236
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
237
+ ucfg = Tempfile.new('unicorn_test_config')
238
+ ucfg.syswrite("listen '#@addr:#@port'\n")
239
+ pid = xfork do
240
+ redirect_test_io do
241
+ exec($unicorn_bin, "-c#{ucfg.path}", "-l#@addr:#{port_cli}")
242
+ end
243
+ end
244
+ results = retry_hit(["http://#@addr:#{port_cli}/"])
245
+ assert_equal String, results[0].class
246
+ results = retry_hit(["http://#@addr:#@port/"])
247
+ assert_equal String, results[0].class
248
+
249
+ port2 = unused_port(@addr)
250
+ ucfg.sysseek(0)
251
+ ucfg.truncate(0)
252
+ ucfg.syswrite("listen '#@addr:#{port2}'\n")
253
+ Process.kill(:HUP, pid)
254
+
255
+ results = retry_hit(["http://#@addr:#{port2}/"])
256
+ assert_equal String, results[0].class
257
+ results = retry_hit(["http://#@addr:#{port_cli}/"])
258
+ assert_equal String, results[0].class
259
+ assert_nothing_raised do
260
+ reuse = TCPServer.new(@addr, @port)
261
+ reuse.close
262
+ end
263
+ assert_shutdown(pid)
264
+ end
265
+
234
266
  def test_unicorn_config_listen_with_options
235
267
  File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
236
268
  ucfg = Tempfile.new('unicorn_test_config')
@@ -254,7 +286,7 @@ end
254
286
  File.unlink(tmp.path)
255
287
  ucfg = Tempfile.new('unicorn_test_config')
256
288
  ucfg.syswrite("listen '#@addr:#@port'\n")
257
- ucfg.syswrite("before_fork { |s,nr|\n")
289
+ ucfg.syswrite("before_fork { |s,w|\n")
258
290
  ucfg.syswrite(" s.listen('#{tmp.path}', :backlog => 5, :sndbuf => 8192)\n")
259
291
  ucfg.syswrite(" s.listen('#@addr:#{port2}', :rcvbuf => 8192)\n")
260
292
  ucfg.syswrite("\n}\n")
@@ -407,6 +439,38 @@ end
407
439
  reexec_basic_test(pid, pid_file)
408
440
  end
409
441
 
442
+ def test_socket_unlinked_restore
443
+ results = nil
444
+ sock = Tempfile.new('unicorn_test_sock')
445
+ sock_path = sock.path
446
+ sock.close!
447
+ ucfg = Tempfile.new('unicorn_test_config')
448
+ ucfg.syswrite("listen \"#{sock_path}\"\n")
449
+
450
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
451
+ pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") } }
452
+ wait_for_file(sock_path)
453
+ assert File.socket?(sock_path)
454
+ assert_nothing_raised do
455
+ sock = UNIXSocket.new(sock_path)
456
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
457
+ results = sock.sysread(4096)
458
+ end
459
+ assert_equal String, results.class
460
+ assert_nothing_raised do
461
+ File.unlink(sock_path)
462
+ Process.kill(:HUP, pid)
463
+ end
464
+ wait_for_file(sock_path)
465
+ assert File.socket?(sock_path)
466
+ assert_nothing_raised do
467
+ sock = UNIXSocket.new(sock_path)
468
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
469
+ results = sock.sysread(4096)
470
+ end
471
+ assert_equal String, results.class
472
+ end
473
+
410
474
  def test_unicorn_config_file
411
475
  pid_file = "#{@tmpdir}/test.pid"
412
476
  sock = Tempfile.new('unicorn_test_sock')
@@ -442,7 +506,7 @@ end
442
506
  assert_equal String, results.class
443
507
 
444
508
  # try reloading the config
445
- sock = Tempfile.new('unicorn_test_sock')
509
+ sock = Tempfile.new('new_test_sock')
446
510
  new_sock_path = sock.path
447
511
  @sockets << new_sock_path
448
512
  sock.close!
@@ -452,6 +516,7 @@ end
452
516
 
453
517
  assert_nothing_raised do
454
518
  ucfg = File.open(ucfg.path, "wb")
519
+ ucfg.syswrite("listen \"#{sock_path}\"\n")
455
520
  ucfg.syswrite("listen \"#{new_sock_path}\"\n")
456
521
  ucfg.syswrite("pid \"#{pid_file}\"\n")
457
522
  ucfg.syswrite("logger Logger.new('#{new_log.path}')\n")
@@ -522,6 +587,7 @@ end
522
587
  end
523
588
 
524
589
  wait_master_ready(log.path)
590
+ File.truncate(log.path, 0)
525
591
  wait_for_file(pid_file)
526
592
  orig_pid = pid = File.read(pid_file).to_i
527
593
  orig_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
@@ -535,6 +601,8 @@ end
535
601
  end
536
602
  wait_for_death(pid)
537
603
 
604
+ wait_master_ready(log.path)
605
+ File.truncate(log.path, 0)
538
606
  wait_for_file(pid_file)
539
607
  pid = File.read(pid_file).to_i
540
608
  assert_not_equal orig_pid, pid
@@ -542,7 +610,7 @@ end
542
610
  assert $?.success?
543
611
 
544
612
  # we could've inherited descriptors the first time around
545
- assert expect_size >= curr_fds.size
613
+ assert expect_size >= curr_fds.size, curr_fds.inspect
546
614
  expect_size = curr_fds.size
547
615
 
548
616
  assert_nothing_raised do
@@ -552,11 +620,13 @@ end
552
620
  end
553
621
  wait_for_death(pid)
554
622
 
623
+ wait_master_ready(log.path)
624
+ File.truncate(log.path, 0)
555
625
  wait_for_file(pid_file)
556
626
  pid = File.read(pid_file).to_i
557
627
  curr_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
558
628
  assert $?.success?
559
- assert_equal expect_size, curr_fds.size
629
+ assert_equal expect_size, curr_fds.size, curr_fds.inspect
560
630
 
561
631
  Process.kill(:QUIT, pid)
562
632
  wait_for_death(pid)
data/test/test_helper.rb CHANGED
@@ -35,8 +35,9 @@ end
35
35
  def redirect_test_io
36
36
  orig_err = STDERR.dup
37
37
  orig_out = STDOUT.dup
38
- STDERR.reopen("test_stderr.#{$$}.log")
39
- STDOUT.reopen("test_stdout.#{$$}.log")
38
+ STDERR.reopen("test_stderr.#{$$}.log", "a")
39
+ STDOUT.reopen("test_stdout.#{$$}.log", "a")
40
+ STDERR.sync = STDOUT.sync = true
40
41
 
41
42
  at_exit do
42
43
  File.unlink("test_stderr.#{$$}.log") rescue nil
@@ -4,7 +4,7 @@ require 'unicorn/configurator'
4
4
 
5
5
  class TestConfigurator < Test::Unit::TestCase
6
6
 
7
- def test_config_defaults
7
+ def test_config_init
8
8
  assert_nothing_raised { Unicorn::Configurator.new {} }
9
9
  end
10
10
 
@@ -91,4 +91,19 @@ class TestConfigurator < Test::Unit::TestCase
91
91
  end
92
92
  end
93
93
 
94
+ def test_after_fork_proc
95
+ [ proc { |a,b| }, Proc.new { |a,b| }, lambda { |a,b| } ].each do |my_proc|
96
+ Unicorn::Configurator.new(:after_fork => my_proc).commit!(self)
97
+ assert_equal my_proc, @after_fork
98
+ end
99
+ end
100
+
101
+ def test_after_fork_wrong_arity
102
+ [ proc { |a| }, Proc.new { }, lambda { |a,b,c| } ].each do |my_proc|
103
+ assert_raises(ArgumentError) do
104
+ Unicorn::Configurator.new(:after_fork => my_proc)
105
+ end
106
+ end
107
+ end
108
+
94
109
  end