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.
- data/CHANGELOG +1 -0
- data/DESIGN +14 -10
- data/GNUmakefile +13 -3
- data/README +28 -21
- data/SIGNALS +11 -4
- data/TODO +0 -2
- data/ext/unicorn/http11/http11.c +17 -34
- data/lib/unicorn.rb +38 -37
- data/lib/unicorn/app/old_rails.rb +9 -3
- data/lib/unicorn/cgi_wrapper.rb +2 -4
- data/lib/unicorn/configurator.rb +29 -19
- data/lib/unicorn/const.rb +1 -1
- data/lib/unicorn/http_request.rb +0 -1
- data/lib/unicorn/socket.rb +17 -57
- data/local.mk.sample +1 -0
- data/test/exec/test_exec.rb +76 -6
- data/test/test_helper.rb +3 -2
- data/test/unit/test_configurator.rb +16 -1
- data/test/unit/test_http_parser.rb +1 -1
- data/test/unit/test_request.rb +53 -0
- data/test/unit/test_server.rb +38 -3
- data/test/unit/test_signals.rb +108 -0
- data/test/unit/test_socket_helper.rb +19 -7
- data/unicorn.gemspec +4 -4
- metadata +3 -2
@@ -13,9 +13,15 @@ class Unicorn::App::OldRails
|
|
13
13
|
|
14
14
|
def call(env)
|
15
15
|
cgi = Unicorn::CGIWrapper.new(env)
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
data/lib/unicorn/cgi_wrapper.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/unicorn/configurator.rb
CHANGED
@@ -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,
|
16
|
-
# server.listen("127.0.0.1:#{9293 +
|
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,
|
29
|
-
server.logger.info("worker=#{
|
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 +
|
36
|
+
# server.listen("127.0.0.1:#{8081 + worker.nr}") rescue nil
|
37
37
|
},
|
38
|
-
:before_fork => lambda { |server,
|
39
|
-
server.logger.info("worker=#{
|
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,
|
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 +
|
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
data/lib/unicorn/http_request.rb
CHANGED
data/lib/unicorn/socket.rb
CHANGED
@@ -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
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
data/test/exec/test_exec.rb
CHANGED
@@ -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,
|
43
|
-
server.logger.info "before_fork: worker=\#{
|
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,
|
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('
|
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
|
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
|