From 29946368c45dce5da116adb426362ee93c507c4e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 5 Oct 2010 00:13:02 +0000 Subject: start using kgio, the kinder, gentler I/O library This should hopefully make the non-blocking accept() situation more tolerable under Ruby 1.9.2. --- lib/unicorn.rb | 28 +++++++++++++--------------- lib/unicorn/http_request.rb | 3 +-- lib/unicorn/socket_helper.rb | 8 ++++---- script/isolate_for_tests | 1 + test/unit/test_request.rb | 4 ++++ unicorn.gemspec | 1 + 6 files changed, 24 insertions(+), 21 deletions(-) diff --git a/lib/unicorn.rb b/lib/unicorn.rb index 418b138..3ee827f 100644 --- a/lib/unicorn.rb +++ b/lib/unicorn.rb @@ -4,6 +4,7 @@ require 'fcntl' require 'etc' require 'stringio' require 'rack' +require 'kgio' require 'unicorn/socket_helper' require 'unicorn/const' require 'unicorn/http_request' @@ -194,7 +195,7 @@ module Unicorn BasicSocket.do_not_reverse_lookup = true # inherit sockets from parents, they need to be plain Socket objects - # before they become UNIXServer or TCPServer + # before they become Kgio::UNIXServer or Kgio::TCPServer inherited = ENV['UNICORN_FD'].to_s.split(/,/).map do |fd| io = Socket.for_fd(fd.to_i) set_server_sockopt(io, listener_opts[sock_name(io)]) @@ -207,9 +208,10 @@ module Unicorn LISTENERS.replace(inherited) # we start out with generic Socket objects that get cast to either - # TCPServer or UNIXServer objects; but since the Socket objects - # share the same OS-level file descriptor as the higher-level *Server - # objects; we need to prevent Socket objects from being garbage-collected + # Kgio::TCPServer or Kgio::UNIXServer objects; but since the Socket + # objects share the same OS-level file descriptor as the higher-level + # *Server objects; we need to prevent Socket objects from being + # garbage-collected config_listeners -= listener_names if config_listeners.empty? && LISTENERS.empty? config_listeners << Unicorn::Const::DEFAULT_LISTEN @@ -320,7 +322,7 @@ module Unicorn tries = opt[:tries] || 5 begin io = bind_listen(address, opt) - unless TCPServer === io || UNIXServer === io + unless Kgio::TCPServer === io || Kgio::UNIXServer === io IO_PURGATORY << io io = server_cast(io) end @@ -449,13 +451,11 @@ module Unicorn # Wake up every second anyways to run murder_lazy_workers def master_sleep(sec) IO.select([ SELF_PIPE[0] ], nil, nil, sec) or return - SELF_PIPE[0].read_nonblock(Const::CHUNK_SIZE) - rescue Errno::EAGAIN, Errno::EINTR + SELF_PIPE[0].kgio_tryread(Const::CHUNK_SIZE) end def awaken_master - SELF_PIPE[1].write_nonblock('.') # wakeup master process from select - rescue Errno::EAGAIN, Errno::EINTR + SELF_PIPE[1].kgio_trywrite('.') # wakeup master process from select end # reaps all unreaped workers @@ -581,7 +581,7 @@ module Unicorn logger.error e.backtrace.join("\n") Const::ERROR_500_RESPONSE end - client.write_nonblock(msg) + client.kgio_trywrite(msg) client.close rescue nil @@ -590,7 +590,6 @@ module Unicorn # once a client is accepted, it is processed in its entirety here # in 3 easy steps: read request, call app, write app response def process_client(client) - client.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) r = app.call(env = REQUEST.read(client)) if 100 == r[0].to_i @@ -665,11 +664,10 @@ module Unicorn alive.chmod(m = 0 == m ? 1 : 0) ready.each do |sock| - begin - process_client(sock.accept_nonblock) + if client = sock.kgio_tryaccept + process_client(client) nr += 1 alive.chmod(m = 0 == m ? 1 : 0) - rescue Errno::EAGAIN, Errno::ECONNABORTED end break if nr < 0 end @@ -773,7 +771,7 @@ module Unicorn def init_self_pipe! SELF_PIPE.each { |io| io.close rescue nil } - SELF_PIPE.replace(IO.pipe) + SELF_PIPE.replace(Kgio::Pipe.new) SELF_PIPE.each { |io| io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) } end diff --git a/lib/unicorn/http_request.rb b/lib/unicorn/http_request.rb index 4355ad7..7519170 100644 --- a/lib/unicorn/http_request.rb +++ b/lib/unicorn/http_request.rb @@ -18,7 +18,6 @@ class Unicorn::HttpRequest } NULL_IO = StringIO.new("") - LOCALHOST = '127.0.0.1' # :stopdoc: # A frozen format for this is about 15% faster @@ -62,7 +61,7 @@ class Unicorn::HttpRequest # identify the client for the immediate request to the server; # that client may be a proxy, gateway, or other intermediary # acting on behalf of the actual source client." - @env[REMOTE_ADDR] = TCPSocket === socket ? socket.peeraddr[-1] : LOCALHOST + @env[REMOTE_ADDR] = socket.kgio_addr # short circuit the common case with small GET requests first if @parser.headers(@env, socket.readpartial(16384, @buf)).nil? diff --git a/lib/unicorn/socket_helper.rb b/lib/unicorn/socket_helper.rb index 1d03eab..7364937 100644 --- a/lib/unicorn/socket_helper.rb +++ b/lib/unicorn/socket_helper.rb @@ -126,12 +126,12 @@ module Unicorn end old_umask = File.umask(opt[:umask] || 0) begin - UNIXServer.new(address) + Kgio::UNIXServer.new(address) ensure File.umask(old_umask) end elsif address =~ /^(\d+\.\d+\.\d+\.\d+):(\d+)$/ - TCPServer.new($1, $2.to_i) + Kgio::TCPServer.new($1, $2.to_i) else raise ArgumentError, "Don't know how to bind: #{address}" end @@ -166,9 +166,9 @@ module Unicorn def server_cast(sock) begin Socket.unpack_sockaddr_in(sock.getsockname) - TCPServer.for_fd(sock.fileno) + Kgio::TCPServer.for_fd(sock.fileno) rescue ArgumentError - UNIXServer.for_fd(sock.fileno) + Kgio::UNIXServer.for_fd(sock.fileno) end end diff --git a/script/isolate_for_tests b/script/isolate_for_tests index 1919289..ac856a0 100755 --- a/script/isolate_for_tests +++ b/script/isolate_for_tests @@ -17,6 +17,7 @@ opts = { pid = fork do Isolate.now!(opts) do gem 'sqlite3-ruby', '1.2.5' + gem 'kgio', '1.1.0' gem 'rack', '1.1.0' end end diff --git a/test/unit/test_request.rb b/test/unit/test_request.rb index 1896300..8aae344 100644 --- a/test/unit/test_request.rb +++ b/test/unit/test_request.rb @@ -12,6 +12,9 @@ class RequestTest < Test::Unit::TestCase class MockRequest < StringIO alias_method :readpartial, :sysread alias_method :read_nonblock, :sysread + def kgio_addr + '127.0.0.1' + end end def setup @@ -159,6 +162,7 @@ class RequestTest < Test::Unit::TestCase buf = (' ' * bs).freeze length = bs * count client = Tempfile.new('big_put') + def client.kgio_addr; '127.0.0.1'; end client.syswrite( "PUT / HTTP/1.1\r\n" \ "Host: foo\r\n" \ diff --git a/unicorn.gemspec b/unicorn.gemspec index 44a6d56..cb155e9 100644 --- a/unicorn.gemspec +++ b/unicorn.gemspec @@ -48,6 +48,7 @@ Gem::Specification.new do |s| # commented out. Nevertheless, upgrading to Rails 2.3.4 or later is # *strongly* recommended for security reasons. s.add_dependency(%q) + s.add_dependency(%q, '~> 1.1.0') s.add_development_dependency('isolate', '~> 2.0.2') -- cgit v1.2.3-24-ge0c7