unicorn 0.92.0 → 0.93.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/.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
|