unicorn 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
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