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/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 ENVIRONMENT",
62
- "use ENVIRONMENT for defaults (default: development)") do |e|
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", "--path PATH", "DEPRECATED") do |v|
83
- warn %q{Use of --path/-P is strongly discouraged}
84
- warn %q{Use the 'map' directive in the rackup config instead}
85
- ENV['RAILS_RELATIVE_URL_ROOT'] = map_path = v
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
@@ -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
- create_makefile("unicorn_http")
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 "/" == name[0..0]
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
- File.open(path, 'wb') { |fp| fp.syswrite("#$$\n") } if path
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, tries = 0.5, 5
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 (#{tries} tries left)"
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
- # useful as an at_exit handler.
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
- if File.exist?(path) && (wpid = File.read(path).to_i) > 1
590
- begin
591
- Process.kill(0, wpid)
592
- return wpid
593
- rescue Errno::ESRCH
594
- end
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
- nil
632
+ rescue Errno::ENOENT
597
633
  end
598
634
 
599
635
  def load_config!
@@ -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..0] == "/" or args[0] = ::File.expand_path(first)
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 = Unicorn::Z.dup
129
- while inp.read(CHUNK_SIZE, buf)
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
@@ -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
- while input.read(16384, buf)
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 = {
@@ -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] += @output_cookies if @output_cookies
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
- parseable_status = @head.delete(Status)
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
- # The stdoutput should be completely bypassed but we'll drop a
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
 
@@ -5,35 +5,68 @@ require 'logger'
5
5
 
6
6
  module Unicorn
7
7
 
8
- # Implements a simple DSL for configuring a unicorn server.
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 '0.0.0.0:9292'
13
+ # listen 9292, :tcp_nopush => true
14
14
  # timeout 10
15
15
  # pid "/tmp/my_app.pid"
16
- # after_fork do |server,worker|
17
- # server.listen("127.0.0.1:#{9293 + worker.nr}") rescue nil
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 => DEFAULT_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
- # # "rescue nil" statement is needed because USR2 will
93
- # # cause the master process to reexecute itself and the
94
- # # per-worker ports can be taken, necessitating another
95
- # # HUP after QUIT-ing the original master:
96
- # server.listen("127.0.0.1:#{9293 + worker.nr}") rescue nil
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.tempfile.chown(target_uid, target_gid)
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
- # +backlog+: this is the backlog of the listen() syscall.
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
- # +rcvbuf+, +sndbuf+: maximum send and receive buffer sizes of sockets
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
- # +tcp_nodelay+: disables Nagle's algorithm on TCP sockets
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
- # +tcp_nopush+: enables TCP_CORK in Linux or TCP_NOPUSH in FreeBSD
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
- Hash === set[:listener_opts] or
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
- private
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