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/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