unicorn 0.92.0 → 0.93.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +2 -0
- data/.gitignore +1 -0
- data/Documentation/unicorn.1.txt +9 -0
- data/Documentation/unicorn_rails.1.txt +19 -0
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +37 -18
- data/HACKING +113 -0
- data/KNOWN_ISSUES +27 -0
- data/README +6 -22
- data/Rakefile +1 -3
- data/SIGNALS +4 -1
- data/TUNING +8 -1
- data/bin/unicorn_rails +12 -8
- data/ext/unicorn_http/extconf.rb +5 -3
- data/lib/unicorn.rb +53 -17
- data/lib/unicorn/app/exec_cgi.rb +4 -4
- data/lib/unicorn/app/inetd.rb +3 -4
- data/lib/unicorn/cgi_wrapper.rb +4 -10
- data/lib/unicorn/configurator.rb +102 -52
- data/lib/unicorn/const.rb +2 -2
- data/lib/unicorn/http_request.rb +9 -18
- data/lib/unicorn/http_response.rb +26 -28
- data/lib/unicorn/socket_helper.rb +1 -1
- data/lib/unicorn/tee_input.rb +2 -2
- data/lib/unicorn/util.rb +3 -3
- data/local.mk.sample +8 -1
- data/test/exec/test_exec.rb +1 -1
- data/test/rails/app-2.3.3.1/public/x.txt +1 -0
- data/test/rails/test_rails.rb +25 -0
- data/test/test_helper.rb +2 -4
- data/test/unit/test_configurator.rb +30 -0
- data/test/unit/test_signals.rb +2 -0
- data/unicorn.gemspec +12 -9
- metadata +24 -10
data/bin/unicorn_rails
CHANGED
@@ -12,7 +12,6 @@ options = { :listeners => listeners }
|
|
12
12
|
host, port = Unicorn::Const::DEFAULT_HOST, Unicorn::Const::DEFAULT_PORT
|
13
13
|
set_listener = false
|
14
14
|
ENV['RAILS_ENV'] ||= "development"
|
15
|
-
map_path = ENV['RAILS_RELATIVE_URL_ROOT']
|
16
15
|
|
17
16
|
opts = OptionParser.new("", 24, ' ') do |opts|
|
18
17
|
opts.banner = "Usage: #{cmd} " \
|
@@ -58,8 +57,8 @@ opts = OptionParser.new("", 24, ' ') do |opts|
|
|
58
57
|
set_listener = true
|
59
58
|
end
|
60
59
|
|
61
|
-
opts.on("-E", "--env
|
62
|
-
"use
|
60
|
+
opts.on("-E", "--env RAILS_ENV",
|
61
|
+
"use RAILS_ENV for defaults (default: development)") do |e|
|
63
62
|
ENV['RAILS_ENV'] = e
|
64
63
|
end
|
65
64
|
|
@@ -79,10 +78,15 @@ opts = OptionParser.new("", 24, ' ') do |opts|
|
|
79
78
|
options[:config_file] = File.expand_path(f)
|
80
79
|
end
|
81
80
|
|
82
|
-
opts.on("-P
|
83
|
-
warn %q{Use of
|
84
|
-
warn %q{Use
|
85
|
-
ENV['RAILS_RELATIVE_URL_ROOT'] =
|
81
|
+
opts.on("-P PATH", "DEPRECATED") do |v|
|
82
|
+
warn %q{Use of -P is ambiguous and discouraged}
|
83
|
+
warn %q{Use --path or RAILS_RELATIVE_URL_ROOT instead}
|
84
|
+
ENV['RAILS_RELATIVE_URL_ROOT'] = v
|
85
|
+
end
|
86
|
+
|
87
|
+
opts.on("--path PATH", "Runs Rails app mounted at a specific path.",
|
88
|
+
"(default: /)") do |v|
|
89
|
+
ENV['RAILS_RELATIVE_URL_ROOT'] = v
|
86
90
|
end
|
87
91
|
|
88
92
|
# I'm avoiding Unicorn-specific config options on the command-line.
|
@@ -156,8 +160,8 @@ app = lambda do ||
|
|
156
160
|
Object.const_get(File.basename(config, '.rb').capitalize)
|
157
161
|
end
|
158
162
|
|
159
|
-
map_path ||= '/'
|
160
163
|
Rack::Builder.new do
|
164
|
+
map_path = ENV['RAILS_RELATIVE_URL_ROOT'] || '/'
|
161
165
|
if inner_app.class.to_s == "Unicorn::App::OldRails"
|
162
166
|
if map_path != '/'
|
163
167
|
# patches + tests welcome, but I really cbf to deal with this
|
data/ext/unicorn_http/extconf.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
require 'mkmf'
|
3
3
|
|
4
|
-
$CFLAGS += " -fPIC " # needed for Rubinius, MRI already uses it regardless
|
5
|
-
|
6
4
|
dir_config("unicorn_http")
|
7
5
|
|
8
6
|
have_macro("SIZEOF_OFF_T", "ruby.h") or check_sizeof("off_t", "sys/types.h")
|
9
7
|
have_macro("SIZEOF_LONG", "ruby.h") or check_sizeof("long", "sys/types.h")
|
10
8
|
have_func("rb_str_set_len", "ruby.h")
|
11
9
|
have_func("rb_str_modify", "ruby.h")
|
12
|
-
|
10
|
+
|
11
|
+
# -fPIC is needed for Rubinius, MRI already uses it regardless
|
12
|
+
with_cflags($CFLAGS + " -fPIC ") do
|
13
|
+
create_makefile("unicorn_http")
|
14
|
+
end
|
data/lib/unicorn.rb
CHANGED
@@ -15,10 +15,6 @@ module Unicorn
|
|
15
15
|
autoload :TeeInput, 'unicorn/tee_input'
|
16
16
|
autoload :Util, 'unicorn/util'
|
17
17
|
|
18
|
-
Z = '' # the stock empty string we use everywhere...
|
19
|
-
Z.force_encoding(Encoding::BINARY) if Z.respond_to?(:force_encoding)
|
20
|
-
Z.freeze
|
21
|
-
|
22
18
|
class << self
|
23
19
|
def run(app, options = {})
|
24
20
|
HttpServer.new(app, options).start.join
|
@@ -65,7 +61,13 @@ module Unicorn
|
|
65
61
|
0 => $0.dup,
|
66
62
|
}
|
67
63
|
|
64
|
+
# This class and its members can be considered a stable interface
|
65
|
+
# and will not change in a backwards-incompatible fashion between
|
66
|
+
# releases of Unicorn. You may need to access it in the
|
67
|
+
# before_fork/after_fork hooks. See the Unicorn::Configurator RDoc
|
68
|
+
# for examples.
|
68
69
|
class Worker < Struct.new(:nr, :tmp)
|
70
|
+
|
69
71
|
# worker objects may be compared to just plain numbers
|
70
72
|
def ==(other_nr)
|
71
73
|
self.nr == other_nr
|
@@ -82,6 +84,17 @@ module Unicorn
|
|
82
84
|
self.init_listeners = options[:listeners] ? options[:listeners].dup : []
|
83
85
|
self.config = Configurator.new(options.merge(:use_defaults => true))
|
84
86
|
self.listener_opts = {}
|
87
|
+
|
88
|
+
# we try inheriting listeners first, so we bind them later.
|
89
|
+
# we don't write the pid file until we've bound listeners in case
|
90
|
+
# unicorn was started twice by mistake. Even though our #pid= method
|
91
|
+
# checks for stale/existing pid files, race conditions are still
|
92
|
+
# possible (and difficult/non-portable to avoid) and can be likely
|
93
|
+
# to clobber the pid if the second start was in quick succession
|
94
|
+
# after the first, so we rely on the listener binding to fail in
|
95
|
+
# that case. Some tests (in and outside of this source tree) and
|
96
|
+
# monitoring tools may also rely on pid files existing before we
|
97
|
+
# attempt to connect to the listener(s)
|
85
98
|
config.commit!(self, :skip => [:listeners, :pid])
|
86
99
|
self.orig_app = app
|
87
100
|
end
|
@@ -127,7 +140,7 @@ module Unicorn
|
|
127
140
|
def listeners=(listeners)
|
128
141
|
cur_names, dead_names = [], []
|
129
142
|
listener_names.each do |name|
|
130
|
-
if
|
143
|
+
if ?/ == name[0]
|
131
144
|
# mark unlinked sockets as dead so we can rebind them
|
132
145
|
(File.socket?(name) ? cur_names : dead_names) << name
|
133
146
|
else
|
@@ -168,17 +181,36 @@ module Unicorn
|
|
168
181
|
end
|
169
182
|
end
|
170
183
|
unlink_pid_safe(pid) if pid
|
171
|
-
|
184
|
+
|
185
|
+
if path
|
186
|
+
fp = begin
|
187
|
+
tmp = "#{File.dirname(path)}/#{rand}.#$$"
|
188
|
+
File.open(tmp, File::RDWR|File::CREAT|File::EXCL, 0644)
|
189
|
+
rescue Errno::EEXIST
|
190
|
+
retry
|
191
|
+
end
|
192
|
+
fp.syswrite("#$$\n")
|
193
|
+
File.rename(fp.path, path)
|
194
|
+
fp.close
|
195
|
+
end
|
172
196
|
self.set_pid(path)
|
173
197
|
end
|
174
198
|
|
175
199
|
# add a given address to the +listeners+ set, idempotently
|
176
200
|
# Allows workers to add a private, per-process listener via the
|
177
201
|
# after_fork hook. Very useful for debugging and testing.
|
202
|
+
# +:tries+ may be specified as an option for the number of times
|
203
|
+
# to retry, and +:delay+ may be specified as the time in seconds
|
204
|
+
# to delay between retries.
|
205
|
+
# A negative value for +:tries+ indicates the listen will be
|
206
|
+
# retried indefinitely, this is useful when workers belonging to
|
207
|
+
# different masters are spawned during a transparent upgrade.
|
178
208
|
def listen(address, opt = {}.merge(listener_opts[address] || {}))
|
209
|
+
address = config.expand_addr(address)
|
179
210
|
return if String === address && listener_names.include?(address)
|
180
211
|
|
181
|
-
delay
|
212
|
+
delay = opt[:delay] || 0.5
|
213
|
+
tries = opt[:tries] || 5
|
182
214
|
begin
|
183
215
|
io = bind_listen(address, opt)
|
184
216
|
unless TCPServer === io || UNIXServer === io
|
@@ -192,7 +224,8 @@ module Unicorn
|
|
192
224
|
logger.error "adding listener failed addr=#{address} (in use)"
|
193
225
|
raise err if tries == 0
|
194
226
|
tries -= 1
|
195
|
-
logger.error "retrying in #{delay} seconds
|
227
|
+
logger.error "retrying in #{delay} seconds " \
|
228
|
+
"(#{tries < 0 ? 'infinite' : tries} tries left)"
|
196
229
|
sleep(delay)
|
197
230
|
retry
|
198
231
|
end
|
@@ -350,7 +383,7 @@ module Unicorn
|
|
350
383
|
logger.error "reexec-ed child already running PID:#{reexec_pid}"
|
351
384
|
return
|
352
385
|
rescue Errno::ESRCH
|
353
|
-
reexec_pid = 0
|
386
|
+
self.reexec_pid = 0
|
354
387
|
end
|
355
388
|
end
|
356
389
|
|
@@ -578,7 +611,9 @@ module Unicorn
|
|
578
611
|
end
|
579
612
|
|
580
613
|
# unlinks a PID file at given +path+ if it contains the current PID
|
581
|
-
#
|
614
|
+
# still potentially racy without locking the directory (which is
|
615
|
+
# non-portable and may interact badly with other programs), but the
|
616
|
+
# window for hitting the race condition is small
|
582
617
|
def unlink_pid_safe(path)
|
583
618
|
(File.read(path).to_i == $$ and File.unlink(path)) rescue nil
|
584
619
|
end
|
@@ -586,14 +621,15 @@ module Unicorn
|
|
586
621
|
# returns a PID if a given path contains a non-stale PID file,
|
587
622
|
# nil otherwise.
|
588
623
|
def valid_pid?(path)
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
624
|
+
wpid = File.read(path).to_i
|
625
|
+
wpid <= 0 and return nil
|
626
|
+
begin
|
627
|
+
Process.kill(0, wpid)
|
628
|
+
return wpid
|
629
|
+
rescue Errno::ESRCH
|
630
|
+
# don't unlink stale pid files, racy without non-portable locking...
|
595
631
|
end
|
596
|
-
|
632
|
+
rescue Errno::ENOENT
|
597
633
|
end
|
598
634
|
|
599
635
|
def load_config!
|
data/lib/unicorn/app/exec_cgi.rb
CHANGED
@@ -36,7 +36,7 @@ module Unicorn::App
|
|
36
36
|
self.args = args
|
37
37
|
first = args[0] or
|
38
38
|
raise ArgumentError, "need path to executable"
|
39
|
-
first[0
|
39
|
+
first[0] == ?/ or args[0] = ::File.expand_path(first)
|
40
40
|
File.executable?(args[0]) or
|
41
41
|
raise ArgumentError, "#{args[0]} is not executable"
|
42
42
|
end
|
@@ -125,10 +125,10 @@ module Unicorn::App
|
|
125
125
|
else
|
126
126
|
tmp = Unicorn::Util.tmpio
|
127
127
|
|
128
|
-
buf =
|
129
|
-
|
128
|
+
buf = inp.read(CHUNK_SIZE)
|
129
|
+
begin
|
130
130
|
tmp.syswrite(buf)
|
131
|
-
end
|
131
|
+
end while inp.read(CHUNK_SIZE, buf)
|
132
132
|
tmp.sysseek(0)
|
133
133
|
tmp
|
134
134
|
end
|
data/lib/unicorn/app/inetd.rb
CHANGED
@@ -33,13 +33,12 @@ module Unicorn::App
|
|
33
33
|
inp_pid = fork {
|
34
34
|
input = env['rack.input']
|
35
35
|
[ err_rd, out_rd ].each { |io| io.close }
|
36
|
-
buf = Unicorn::Z.dup
|
37
36
|
|
38
37
|
# this is dependent on input.read having readpartial semantics:
|
39
|
-
|
38
|
+
buf = input.read(16384)
|
39
|
+
begin
|
40
40
|
in_wr.write(buf)
|
41
|
-
end
|
42
|
-
in_wr.close
|
41
|
+
end while input.read(16384, buf)
|
43
42
|
}
|
44
43
|
in_wr.close
|
45
44
|
self.pid_map = {
|
data/lib/unicorn/cgi_wrapper.rb
CHANGED
@@ -59,21 +59,20 @@ class Unicorn::CGIWrapper < ::CGI
|
|
59
59
|
@status = nil
|
60
60
|
@head = {}
|
61
61
|
@headv = Hash.new { |hash,key| hash[key] = [] }
|
62
|
-
@body = StringIO.new
|
62
|
+
@body = StringIO.new("")
|
63
63
|
super(*args)
|
64
64
|
end
|
65
65
|
|
66
66
|
# finalizes the response in a way Rack applications would expect
|
67
67
|
def rack_response
|
68
68
|
# @head[CONTENT_LENGTH] ||= @body.size
|
69
|
-
@headv[SET_COOKIE]
|
69
|
+
@headv[SET_COOKIE].concat(@output_cookies) if @output_cookies
|
70
70
|
@headv.each_pair do |key,value|
|
71
71
|
@head[key] ||= value.join("\n") unless value.empty?
|
72
72
|
end
|
73
73
|
|
74
74
|
# Capitalized "Status:", with human-readable status code (e.g. "200 OK")
|
75
|
-
|
76
|
-
@status ||= parseable_status.split(/ /)[0].to_i rescue 500
|
75
|
+
@status ||= @head.delete(Status)
|
77
76
|
|
78
77
|
[ @status || 500, @head, [ @body.string ] ]
|
79
78
|
end
|
@@ -138,13 +137,8 @@ class Unicorn::CGIWrapper < ::CGI
|
|
138
137
|
@env_table[RACK_INPUT]
|
139
138
|
end
|
140
139
|
|
141
|
-
#
|
142
|
-
# warning just in case
|
140
|
+
# return a pointer to the StringIO body since it's STDOUT-like
|
143
141
|
def stdoutput
|
144
|
-
err = @env_table[RACK_ERRORS]
|
145
|
-
err.puts "WARNING: Your program is doing something not expected."
|
146
|
-
err.puts "Please tell Eric that stdoutput was used and what software " \
|
147
|
-
"you are running. Thanks."
|
148
142
|
@body
|
149
143
|
end
|
150
144
|
|
data/lib/unicorn/configurator.rb
CHANGED
@@ -5,35 +5,68 @@ require 'logger'
|
|
5
5
|
|
6
6
|
module Unicorn
|
7
7
|
|
8
|
-
# Implements a simple DSL for configuring a
|
8
|
+
# Implements a simple DSL for configuring a Unicorn server.
|
9
9
|
#
|
10
10
|
# Example (when used with the unicorn config file):
|
11
11
|
# worker_processes 4
|
12
12
|
# listen '/tmp/my_app.sock', :backlog => 1
|
13
|
-
# listen
|
13
|
+
# listen 9292, :tcp_nopush => true
|
14
14
|
# timeout 10
|
15
15
|
# pid "/tmp/my_app.pid"
|
16
|
-
#
|
17
|
-
#
|
16
|
+
#
|
17
|
+
# # combine REE with "preload_app true" for memory savings
|
18
|
+
# # http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
|
19
|
+
# preload_app true
|
20
|
+
# GC.respond_to?(:copy_on_write_friendly=) and
|
21
|
+
# GC.copy_on_write_friendly = true
|
22
|
+
#
|
23
|
+
# before_fork do |server, worker|
|
24
|
+
# # the following is recomended for Rails + "preload_app true"
|
25
|
+
# # as there's no need for the master process to hold a connection
|
26
|
+
# defined?(ActiveRecord::Base) and
|
27
|
+
# ActiveRecord::Base.connection.disconnect!
|
28
|
+
#
|
29
|
+
# # the following allows a new master process to incrementally
|
30
|
+
# # phase out the old master process with SIGTTOU to avoid a
|
31
|
+
# # thundering herd (especially in the "preload_app false" case)
|
32
|
+
# # when doing a transparent upgrade. The last worker spawned
|
33
|
+
# # will then kill off the old master process with a SIGQUIT.
|
34
|
+
# old_pid = "#{server.config[:pid]}.oldbin"
|
35
|
+
# if old_pid != server.pid
|
36
|
+
# begin
|
37
|
+
# sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
|
38
|
+
# Process.kill(sig, File.read(old_pid).to_i)
|
39
|
+
# rescue Errno::ENOENT, Errno::ESRCH
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# # optionally throttle the master from forking too quickly by sleeping
|
43
|
+
# sleep 1
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# after_fork do |server, worker|
|
47
|
+
# # per-process listener ports for debugging/admin/migrations
|
48
|
+
# addr = "127.0.0.1:#{9293 + worker.nr}"
|
49
|
+
# server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)
|
50
|
+
#
|
51
|
+
# # the following is required for Rails + "preload_app true",
|
52
|
+
# defined?(ActiveRecord::Base) and
|
53
|
+
# ActiveRecord::Base.establish_connection
|
54
|
+
#
|
55
|
+
# # if preload_app is true, then you may also want to check and
|
56
|
+
# # restart any other shared sockets/descriptors such as Memcached,
|
57
|
+
# # and Redis. TokyoCabinet file handles are safe to reuse
|
58
|
+
# # between any number of forked children (assuming your kernel
|
59
|
+
# # correctly implements pread()/pwrite() system calls)
|
18
60
|
# end
|
19
61
|
class Configurator < Struct.new(:set, :config_file)
|
20
|
-
# The default logger writes its output to $stderr
|
21
|
-
DEFAULT_LOGGER = Logger.new($stderr)
|
22
62
|
|
23
63
|
# Default settings for Unicorn
|
24
64
|
DEFAULTS = {
|
25
65
|
:timeout => 60,
|
26
|
-
:logger =>
|
66
|
+
:logger => Logger.new($stderr),
|
27
67
|
:worker_processes => 1,
|
28
68
|
:after_fork => lambda { |server, worker|
|
29
69
|
server.logger.info("worker=#{worker.nr} spawned pid=#{$$}")
|
30
|
-
|
31
|
-
# per-process listener ports for debugging/admin:
|
32
|
-
# "rescue nil" statement is needed because USR2 will
|
33
|
-
# cause the master process to reexecute itself and the
|
34
|
-
# per-worker ports can be taken, necessitating another
|
35
|
-
# HUP after QUIT-ing the original master:
|
36
|
-
# server.listen("127.0.0.1:#{8081 + worker.nr}") rescue nil
|
37
70
|
},
|
38
71
|
:before_fork => lambda { |server, worker|
|
39
72
|
server.logger.info("worker=#{worker.nr} spawning...")
|
@@ -51,6 +84,9 @@ module Unicorn
|
|
51
84
|
self.config_file = defaults.delete(:config_file)
|
52
85
|
set.merge!(DEFAULTS) if use_defaults
|
53
86
|
defaults.each { |key, value| self.send(key, value) }
|
87
|
+
Hash === set[:listener_opts] or
|
88
|
+
set[:listener_opts] = Hash.new { |hash,key| hash[key] = {} }
|
89
|
+
Array === set[:listeners] or set[:listeners] = []
|
54
90
|
reload
|
55
91
|
end
|
56
92
|
|
@@ -89,11 +125,13 @@ module Unicorn
|
|
89
125
|
#
|
90
126
|
# after_fork do |server,worker|
|
91
127
|
# # per-process listener ports for debugging/admin:
|
92
|
-
#
|
93
|
-
#
|
94
|
-
# #
|
95
|
-
# #
|
96
|
-
#
|
128
|
+
# addr = "127.0.0.1:#{9293 + worker.nr}"
|
129
|
+
#
|
130
|
+
# # the negative :tries parameter indicates we will retry forever
|
131
|
+
# # waiting on the existing process to exit with a 5 second :delay
|
132
|
+
# # Existing options for Unicorn::Configurator#listen such as
|
133
|
+
# # :backlog, :rcvbuf, :sndbuf are available here as well.
|
134
|
+
# server.listen(addr, :tries => -1, :delay => 5, :backlog => 128)
|
97
135
|
#
|
98
136
|
# # drop permissions to "www-data" in the worker
|
99
137
|
# # generally there's no reason to start Unicorn as a priviledged user
|
@@ -102,7 +140,7 @@ module Unicorn
|
|
102
140
|
# user, group = 'www-data', 'www-data'
|
103
141
|
# target_uid = Etc.getpwnam(user).uid
|
104
142
|
# target_gid = Etc.getgrnam(group).gid
|
105
|
-
# worker.
|
143
|
+
# worker.tmp.chown(target_uid, target_gid)
|
106
144
|
# if uid != target_uid || gid != target_gid
|
107
145
|
# Process.initgroups(user, target_gid)
|
108
146
|
# Process::GID.change_privilege(target_gid)
|
@@ -170,7 +208,7 @@ module Unicorn
|
|
170
208
|
#
|
171
209
|
# The following options may be specified (but are generally not needed):
|
172
210
|
#
|
173
|
-
#
|
211
|
+
# +:backlog+: this is the backlog of the listen() syscall.
|
174
212
|
#
|
175
213
|
# Some operating systems allow negative values here to specify the
|
176
214
|
# maximum allowable value. In most cases, this number is only
|
@@ -185,7 +223,7 @@ module Unicorn
|
|
185
223
|
#
|
186
224
|
# Default: 1024
|
187
225
|
#
|
188
|
-
#
|
226
|
+
# +:rcvbuf+, +:sndbuf+: maximum receive and send buffer sizes of sockets
|
189
227
|
#
|
190
228
|
# These correspond to the SO_RCVBUF and SO_SNDBUF settings which
|
191
229
|
# can be set via the setsockopt(2) syscall. Some kernels
|
@@ -199,13 +237,13 @@ module Unicorn
|
|
199
237
|
#
|
200
238
|
# Defaults: operating system defaults
|
201
239
|
#
|
202
|
-
#
|
240
|
+
# +:tcp_nodelay+: disables Nagle's algorithm on TCP sockets
|
203
241
|
#
|
204
242
|
# This has no effect on UNIX sockets.
|
205
243
|
#
|
206
244
|
# Default: operating system defaults (usually Nagle's algorithm enabled)
|
207
245
|
#
|
208
|
-
#
|
246
|
+
# +:tcp_nopush+: enables TCP_CORK in Linux or TCP_NOPUSH in FreeBSD
|
209
247
|
#
|
210
248
|
# This will prevent partial TCP frames from being sent out.
|
211
249
|
# Enabling +tcp_nopush+ is generally not needed or recommended as
|
@@ -215,12 +253,21 @@ module Unicorn
|
|
215
253
|
#
|
216
254
|
# This has no effect on UNIX sockets.
|
217
255
|
#
|
256
|
+
# +:tries+: times to retry binding a socket if it is already in use
|
257
|
+
#
|
258
|
+
# A negative number indicates we will retry indefinitely, this is
|
259
|
+
# useful for migrations and upgrades when individual workers
|
260
|
+
# are binding to different ports.
|
261
|
+
#
|
262
|
+
# Default: 5
|
263
|
+
#
|
264
|
+
# +:delay+: seconds to wait between successive +tries+
|
265
|
+
#
|
266
|
+
# Default: 0.5 seconds
|
218
267
|
def listen(address, opt = {})
|
219
268
|
address = expand_addr(address)
|
220
269
|
if String === address
|
221
|
-
|
222
|
-
set[:listener_opts] = Hash.new { |hash,key| hash[key] = {} }
|
223
|
-
[ :backlog, :sndbuf, :rcvbuf ].each do |key|
|
270
|
+
[ :backlog, :sndbuf, :rcvbuf, :tries ].each do |key|
|
224
271
|
value = opt[key] or next
|
225
272
|
Integer === value or
|
226
273
|
raise ArgumentError, "not an integer: #{key}=#{value.inspect}"
|
@@ -230,10 +277,13 @@ module Unicorn
|
|
230
277
|
TrueClass === value || FalseClass === value or
|
231
278
|
raise ArgumentError, "not boolean: #{key}=#{value.inspect}"
|
232
279
|
end
|
280
|
+
unless (value = opt[:delay]).nil?
|
281
|
+
Numeric === value or
|
282
|
+
raise ArgumentError, "not numeric: delay=#{value.inspect}"
|
283
|
+
end
|
233
284
|
set[:listener_opts][address].merge!(opt)
|
234
285
|
end
|
235
286
|
|
236
|
-
set[:listeners] = [] unless Array === set[:listeners]
|
237
287
|
set[:listeners] << address
|
238
288
|
end
|
239
289
|
|
@@ -277,7 +327,30 @@ module Unicorn
|
|
277
327
|
set_path(:stdout_path, path)
|
278
328
|
end
|
279
329
|
|
280
|
-
|
330
|
+
# expands "unix:path/to/foo" to a socket relative to the current path
|
331
|
+
# expands pathnames of sockets if relative to "~" or "~username"
|
332
|
+
# expands "*:port and ":port" to "0.0.0.0:port"
|
333
|
+
def expand_addr(address) #:nodoc
|
334
|
+
return "0.0.0.0:#{address}" if Integer === address
|
335
|
+
return address unless String === address
|
336
|
+
|
337
|
+
case address
|
338
|
+
when %r{\Aunix:(.*)\z}
|
339
|
+
File.expand_path($1)
|
340
|
+
when %r{\A~}
|
341
|
+
File.expand_path(address)
|
342
|
+
when %r{\A(?:\*:)?(\d+)\z}
|
343
|
+
"0.0.0.0:#$1"
|
344
|
+
when %r{\A(.*):(\d+)\z}
|
345
|
+
# canonicalize the name
|
346
|
+
packed = Socket.pack_sockaddr_in($2.to_i, $1)
|
347
|
+
Socket.unpack_sockaddr_in(packed).reverse!.join(':')
|
348
|
+
else
|
349
|
+
address
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
private
|
281
354
|
|
282
355
|
def set_path(var, path) #:nodoc:
|
283
356
|
case path
|
@@ -308,28 +381,5 @@ module Unicorn
|
|
308
381
|
set[var] = my_proc
|
309
382
|
end
|
310
383
|
|
311
|
-
# expands "unix:path/to/foo" to a socket relative to the current path
|
312
|
-
# expands pathnames of sockets if relative to "~" or "~username"
|
313
|
-
# expands "*:port and ":port" to "0.0.0.0:port"
|
314
|
-
def expand_addr(address) #:nodoc
|
315
|
-
return "0.0.0.0:#{address}" if Integer === address
|
316
|
-
return address unless String === address
|
317
|
-
|
318
|
-
case address
|
319
|
-
when %r{\Aunix:(.*)\z}
|
320
|
-
File.expand_path($1)
|
321
|
-
when %r{\A~}
|
322
|
-
File.expand_path(address)
|
323
|
-
when %r{\A(?:\*:)?(\d+)\z}
|
324
|
-
"0.0.0.0:#$1"
|
325
|
-
when %r{\A(.*):(\d+)\z}
|
326
|
-
# canonicalize the name
|
327
|
-
packed = Socket.pack_sockaddr_in($2.to_i, $1)
|
328
|
-
Socket.unpack_sockaddr_in(packed).reverse!.join(':')
|
329
|
-
else
|
330
|
-
address
|
331
|
-
end
|
332
|
-
end
|
333
|
-
|
334
384
|
end
|
335
385
|
end
|