unicorn 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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