unicorn 0.8.1 → 0.8.2

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 CHANGED
@@ -1,3 +1,4 @@
1
+ v0.8.2 - socket handling bugfixes and usability tweaks
1
2
  v0.8.1 - safer timeout handling, more consistent reload behavior
2
3
  v0.8.0 - enforce Rack dependency, minor performance improvements and fixes
3
4
  v0.7.1 - minor fixes, cleanups and documentation improvements
data/lib/unicorn.rb CHANGED
@@ -51,7 +51,7 @@ module Unicorn
51
51
  # don't rely on Dir.pwd here since it's not symlink-aware, and
52
52
  # symlink dirs are the default with Capistrano...
53
53
  :cwd => `/bin/sh -c pwd`.chomp("\n"),
54
- :zero => $0.dup,
54
+ 0 => $0.dup,
55
55
  }
56
56
 
57
57
  Worker = Struct.new(:nr, :tempfile) unless defined?(Worker)
@@ -168,16 +168,23 @@ module Unicorn
168
168
  def listen(address, opt = {}.merge(@listener_opts[address] || {}))
169
169
  return if String === address && listener_names.include?(address)
170
170
 
171
- if io = bind_listen(address, opt)
171
+ delay, tries = 0.5, 5
172
+ begin
173
+ io = bind_listen(address, opt)
172
174
  unless TCPServer === io || UNIXServer === io
173
175
  IO_PURGATORY << io
174
176
  io = server_cast(io)
175
177
  end
176
178
  logger.info "listening on addr=#{sock_name(io)} fd=#{io.fileno}"
177
179
  LISTENERS << io
178
- else
180
+ return io
181
+ rescue Errno::EADDRINUSE => err
179
182
  logger.error "adding listener failed addr=#{address} (in use)"
180
- raise Errno::EADDRINUSE, address
183
+ raise err if tries == 0
184
+ tries -= 1
185
+ logger.error "retrying in #{delay} seconds (#{tries} tries left)"
186
+ sleep(delay)
187
+ retry
181
188
  end
182
189
  end
183
190
 
@@ -253,6 +260,7 @@ module Unicorn
253
260
 
254
261
  # Terminates all workers, but does not exit master process
255
262
  def stop(graceful = true)
263
+ self.listeners = []
256
264
  kill_each_worker(graceful ? :QUIT : :TERM)
257
265
  timeleft = @timeout
258
266
  step = 0.2
@@ -263,8 +271,6 @@ module Unicorn
263
271
  (timeleft -= step) > 0 and next
264
272
  kill_each_worker(:KILL)
265
273
  end
266
- ensure
267
- self.listeners = []
268
274
  end
269
275
 
270
276
  private
@@ -357,7 +363,7 @@ module Unicorn
357
363
  listener_fds = LISTENERS.map { |sock| sock.fileno }
358
364
  ENV['UNICORN_FD'] = listener_fds.join(',')
359
365
  Dir.chdir(START_CTX[:cwd])
360
- cmd = [ START_CTX[:zero] ] + START_CTX[:argv]
366
+ cmd = [ START_CTX[0] ].concat(START_CTX[:argv])
361
367
 
362
368
  # avoid leaking FDs we don't know about, but let before_exec
363
369
  # unset FD_CLOEXEC, if anything else in the app eventually
@@ -430,6 +436,7 @@ module Unicorn
430
436
  # once a client is accepted, it is processed in its entirety here
431
437
  # in 3 easy steps: read request, call app, write app response
432
438
  def process_client(app, client)
439
+ client.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
433
440
  HttpResponse.write(client, app.call(REQUEST.read(client)))
434
441
  # if we get any error, try to write something back to the client
435
442
  # assuming we haven't closed the socket, but don't get hung up
@@ -453,7 +460,7 @@ module Unicorn
453
460
  # traps for USR1, USR2, and HUP may be set in the @after_fork Proc
454
461
  # by the user.
455
462
  def init_worker_process(worker)
456
- QUEUE_SIGS.each { |sig| trap(sig, 'IGNORE') }
463
+ QUEUE_SIGS.each { |sig| trap(sig, nil) }
457
464
  trap(:CHLD, 'DEFAULT')
458
465
  SIG_QUEUE.clear
459
466
  proc_name "worker[#{worker.nr}]"
@@ -609,8 +616,8 @@ module Unicorn
609
616
  end
610
617
 
611
618
  def proc_name(tag)
612
- $0 = ([ File.basename(START_CTX[:zero]), tag ] +
613
- START_CTX[:argv]).join(' ')
619
+ $0 = ([ File.basename(START_CTX[0]), tag
620
+ ]).concat(START_CTX[:argv]).join(' ')
614
621
  end
615
622
 
616
623
  def redirect_io(io, path)
@@ -207,7 +207,24 @@ module Unicorn
207
207
  # specified.
208
208
  #
209
209
  # Defaults: operating system defaults
210
- def listen(address, opt = { :backlog => 1024 })
210
+ #
211
+ # +tcp_nodelay+: disables Nagle's algorithm on TCP sockets
212
+ #
213
+ # This has no effect on UNIX sockets.
214
+ #
215
+ # Default: operating system defaults (usually Nagle's algorithm enabled)
216
+ #
217
+ # +tcp_nopush+: enables TCP_CORK in Linux or TCP_NOPUSH in FreeBSD
218
+ #
219
+ # This will prevent partial TCP frames from being sent out.
220
+ # Enabling +tcp_nopush+ is generally not needed or recommended as
221
+ # controlling +tcp_nodelay+ already provides sufficient latency
222
+ # reduction whereas Unicorn does not know when the best times are
223
+ # for flushing corked sockets.
224
+ #
225
+ # This has no effect on UNIX sockets.
226
+ #
227
+ def listen(address, opt = {})
211
228
  address = expand_addr(address)
212
229
  if String === address
213
230
  Hash === @set[:listener_opts] or
@@ -217,6 +234,11 @@ module Unicorn
217
234
  Integer === value or
218
235
  raise ArgumentError, "not an integer: #{key}=#{value.inspect}"
219
236
  end
237
+ [ :tcp_nodelay, :tcp_nopush ].each do |key|
238
+ (value = opt[key]).nil? and next
239
+ TrueClass === value || FalseClass === value or
240
+ raise ArgumentError, "not boolean: #{key}=#{value.inspect}"
241
+ end
220
242
  @set[:listener_opts][address].merge!(opt)
221
243
  end
222
244
 
data/lib/unicorn/const.rb CHANGED
@@ -5,7 +5,7 @@ module Unicorn
5
5
  # gave about a 3% to 10% performance improvement over using the strings directly.
6
6
  # Symbols did not really improve things much compared to constants.
7
7
  module Const
8
- UNICORN_VERSION="0.8.1".freeze
8
+ UNICORN_VERSION="0.8.2".freeze
9
9
 
10
10
  DEFAULT_HOST = "0.0.0.0".freeze # default TCP listen host address
11
11
  DEFAULT_PORT = "8080".freeze # default TCP listen port
@@ -4,8 +4,56 @@ module Unicorn
4
4
  module SocketHelper
5
5
  include Socket::Constants
6
6
 
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
+ # Do nothing for httpready, just closing a bug when freebsd <= 5.4
15
+ TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH)
16
+ when /freebsd/
17
+ TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH)
18
+ # Use the HTTP accept filter if available.
19
+ # The struct made by pack() is defined in /usr/include/sys/socket.h
20
+ # as accept_filter_arg
21
+ # We won't be seupportin the "dataready" filter unlike nginx
22
+ # since we only support HTTP and no other protocols
23
+ unless `/sbin/sysctl -nq net.inet.accf.http`.empty?
24
+ HTTPREADY = ['httpready', nil].pack('a16a240').freeze
25
+ end
26
+ end
27
+
28
+ def set_tcp_sockopt(sock, opt)
29
+
30
+ # highly portable, but off by default because we don't do keepalive
31
+ if defined?(TCP_NODELAY) && ! (val = opt[:tcp_nodelay]).nil?
32
+ sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, val ? 1 : 0) rescue nil
33
+ end
34
+
35
+ unless (val = opt[:tcp_nopush]).nil?
36
+ val = val ? 1 : 0
37
+ if defined?(TCP_CORK) # Linux
38
+ sock.setsockopt(IPPROTO_TCP, TCP_CORK, val) rescue nil
39
+ elsif defined?(TCP_NOPUSH) # TCP_NOPUSH is untested (FreeBSD)
40
+ sock.setsockopt(IPPROTO_TCP, TCP_NOPUSH, val) rescue nil
41
+ end
42
+ end
43
+
44
+ # No good reason to ever have deferred accepts off
45
+ if defined?(TCP_DEFER_ACCEPT)
46
+ sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, 1) rescue nil
47
+ elsif defined?(SO_ACCEPTFILTER) && defined?(HTTPREADY)
48
+ sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER, HTTPREADY) rescue nil
49
+ end
50
+ end
51
+
7
52
  def set_server_sockopt(sock, opt)
8
53
  opt ||= {}
54
+
55
+ TCPSocket === sock and set_tcp_sockopt(sock, opt)
56
+
9
57
  if opt[:rcvbuf] || opt[:sndbuf]
10
58
  log_buffer_sizes(sock, "before: ")
11
59
  sock.setsockopt(SOL_SOCKET, SO_RCVBUF, opt[:rcvbuf]) if opt[:rcvbuf]
@@ -25,7 +73,7 @@ module Unicorn
25
73
  # creates a new server, socket. address may be a HOST:PORT or
26
74
  # an absolute path to a UNIX socket. address can even be a Socket
27
75
  # object in which case it is immediately returned
28
- def bind_listen(address = '0.0.0.0:8080', opt = { :backlog => 1024 })
76
+ def bind_listen(address = '0.0.0.0:8080', opt = {})
29
77
  return address unless String === address
30
78
 
31
79
  sock = if address[0..0] == "/"
data/unicorn.gemspec CHANGED
@@ -2,23 +2,22 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{unicorn}
5
- s.version = "0.8.1"
5
+ s.version = "0.8.2"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Eric Wong"]
9
- s.date = %q{2009-05-28}
9
+ s.date = %q{2009-07-09}
10
10
  s.description = %q{Rack HTTP server for Unix, fast clients and nothing else}
11
11
  s.email = %q{normalperson@yhbt.net}
12
12
  s.executables = ["unicorn", "unicorn_rails"]
13
13
  s.extensions = ["ext/unicorn/http11/extconf.rb"]
14
14
  s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "README", "TODO", "bin/unicorn", "bin/unicorn_rails", "ext/unicorn/http11/ext_help.h", "ext/unicorn/http11/extconf.rb", "ext/unicorn/http11/http11.c", "ext/unicorn/http11/http11_parser.h", "ext/unicorn/http11/http11_parser.rl", "ext/unicorn/http11/http11_parser_common.rl", "lib/unicorn.rb", "lib/unicorn/app/exec_cgi.rb", "lib/unicorn/app/old_rails.rb", "lib/unicorn/app/old_rails/static.rb", "lib/unicorn/cgi_wrapper.rb", "lib/unicorn/configurator.rb", "lib/unicorn/const.rb", "lib/unicorn/http_request.rb", "lib/unicorn/http_response.rb", "lib/unicorn/launcher.rb", "lib/unicorn/socket_helper.rb", "lib/unicorn/util.rb"]
15
15
  s.files = [".document", ".gitignore", "CHANGELOG", "CONTRIBUTORS", "DESIGN", "GNUmakefile", "LICENSE", "Manifest", "PHILOSOPHY", "README", "Rakefile", "SIGNALS", "TODO", "TUNING", "bin/unicorn", "bin/unicorn_rails", "examples/init.sh", "ext/unicorn/http11/ext_help.h", "ext/unicorn/http11/extconf.rb", "ext/unicorn/http11/http11.c", "ext/unicorn/http11/http11_parser.h", "ext/unicorn/http11/http11_parser.rl", "ext/unicorn/http11/http11_parser_common.rl", "lib/unicorn.rb", "lib/unicorn/app/exec_cgi.rb", "lib/unicorn/app/old_rails.rb", "lib/unicorn/app/old_rails/static.rb", "lib/unicorn/cgi_wrapper.rb", "lib/unicorn/configurator.rb", "lib/unicorn/const.rb", "lib/unicorn/http_request.rb", "lib/unicorn/http_response.rb", "lib/unicorn/launcher.rb", "lib/unicorn/socket_helper.rb", "lib/unicorn/util.rb", "local.mk.sample", "setup.rb", "test/aggregate.rb", "test/benchmark/README", "test/benchmark/big_request.rb", "test/benchmark/dd.ru", "test/benchmark/request.rb", "test/benchmark/response.rb", "test/exec/README", "test/exec/test_exec.rb", "test/rails/app-1.2.3/.gitignore", "test/rails/app-1.2.3/Rakefile", "test/rails/app-1.2.3/app/controllers/application.rb", "test/rails/app-1.2.3/app/controllers/foo_controller.rb", "test/rails/app-1.2.3/app/helpers/application_helper.rb", "test/rails/app-1.2.3/config/boot.rb", "test/rails/app-1.2.3/config/database.yml", "test/rails/app-1.2.3/config/environment.rb", "test/rails/app-1.2.3/config/environments/development.rb", "test/rails/app-1.2.3/config/environments/production.rb", "test/rails/app-1.2.3/config/routes.rb", "test/rails/app-1.2.3/db/.gitignore", "test/rails/app-1.2.3/log/.gitignore", "test/rails/app-1.2.3/public/404.html", "test/rails/app-1.2.3/public/500.html", "test/rails/app-2.0.2/.gitignore", "test/rails/app-2.0.2/Rakefile", "test/rails/app-2.0.2/app/controllers/application.rb", "test/rails/app-2.0.2/app/controllers/foo_controller.rb", "test/rails/app-2.0.2/app/helpers/application_helper.rb", "test/rails/app-2.0.2/config/boot.rb", "test/rails/app-2.0.2/config/database.yml", "test/rails/app-2.0.2/config/environment.rb", "test/rails/app-2.0.2/config/environments/development.rb", "test/rails/app-2.0.2/config/environments/production.rb", "test/rails/app-2.0.2/config/routes.rb", "test/rails/app-2.0.2/db/.gitignore", "test/rails/app-2.0.2/log/.gitignore", "test/rails/app-2.0.2/public/404.html", "test/rails/app-2.0.2/public/500.html", "test/rails/app-2.1.2/.gitignore", "test/rails/app-2.1.2/Rakefile", "test/rails/app-2.1.2/app/controllers/application.rb", "test/rails/app-2.1.2/app/controllers/foo_controller.rb", "test/rails/app-2.1.2/app/helpers/application_helper.rb", "test/rails/app-2.1.2/config/boot.rb", "test/rails/app-2.1.2/config/database.yml", "test/rails/app-2.1.2/config/environment.rb", "test/rails/app-2.1.2/config/environments/development.rb", "test/rails/app-2.1.2/config/environments/production.rb", "test/rails/app-2.1.2/config/routes.rb", "test/rails/app-2.1.2/db/.gitignore", "test/rails/app-2.1.2/log/.gitignore", "test/rails/app-2.1.2/public/404.html", "test/rails/app-2.1.2/public/500.html", "test/rails/app-2.2.2/.gitignore", "test/rails/app-2.2.2/Rakefile", "test/rails/app-2.2.2/app/controllers/application.rb", "test/rails/app-2.2.2/app/controllers/foo_controller.rb", "test/rails/app-2.2.2/app/helpers/application_helper.rb", "test/rails/app-2.2.2/config/boot.rb", "test/rails/app-2.2.2/config/database.yml", "test/rails/app-2.2.2/config/environment.rb", "test/rails/app-2.2.2/config/environments/development.rb", "test/rails/app-2.2.2/config/environments/production.rb", "test/rails/app-2.2.2/config/routes.rb", "test/rails/app-2.2.2/db/.gitignore", "test/rails/app-2.2.2/log/.gitignore", "test/rails/app-2.2.2/public/404.html", "test/rails/app-2.2.2/public/500.html", "test/rails/app-2.3.2.1/.gitignore", "test/rails/app-2.3.2.1/Rakefile", "test/rails/app-2.3.2.1/app/controllers/application_controller.rb", "test/rails/app-2.3.2.1/app/controllers/foo_controller.rb", "test/rails/app-2.3.2.1/app/helpers/application_helper.rb", "test/rails/app-2.3.2.1/config/boot.rb", "test/rails/app-2.3.2.1/config/database.yml", "test/rails/app-2.3.2.1/config/environment.rb", "test/rails/app-2.3.2.1/config/environments/development.rb", "test/rails/app-2.3.2.1/config/environments/production.rb", "test/rails/app-2.3.2.1/config/routes.rb", "test/rails/app-2.3.2.1/db/.gitignore", "test/rails/app-2.3.2.1/log/.gitignore", "test/rails/app-2.3.2.1/public/404.html", "test/rails/app-2.3.2.1/public/500.html", "test/rails/test_rails.rb", "test/test_helper.rb", "test/unit/test_configurator.rb", "test/unit/test_http_parser.rb", "test/unit/test_request.rb", "test/unit/test_response.rb", "test/unit/test_server.rb", "test/unit/test_signals.rb", "test/unit/test_socket_helper.rb", "test/unit/test_upload.rb", "test/unit/test_util.rb", "unicorn.gemspec"]
16
- s.has_rdoc = true
17
16
  s.homepage = %q{http://unicorn.bogomips.org/}
18
17
  s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Unicorn", "--main", "README"]
19
18
  s.require_paths = ["lib", "ext"]
20
19
  s.rubyforge_project = %q{unicorn}
21
- s.rubygems_version = %q{1.3.2}
20
+ s.rubygems_version = %q{1.3.4}
22
21
  s.summary = %q{Rack HTTP server for Unix, fast clients and nothing else}
23
22
  s.test_files = ["test/unit/test_configurator.rb", "test/unit/test_response.rb", "test/unit/test_request.rb", "test/unit/test_signals.rb", "test/unit/test_upload.rb", "test/unit/test_http_parser.rb", "test/unit/test_socket_helper.rb", "test/unit/test_util.rb", "test/unit/test_server.rb"]
24
23
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unicorn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Wong
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-05-28 00:00:00 -07:00
12
+ date: 2009-07-09 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -217,7 +217,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
217
217
  requirements: []
218
218
 
219
219
  rubyforge_project: unicorn
220
- rubygems_version: 1.3.2
220
+ rubygems_version: 1.3.4
221
221
  signing_key:
222
222
  specification_version: 3
223
223
  summary: Rack HTTP server for Unix, fast clients and nothing else